Skip to content

Latest commit

 

History

History
387 lines (260 loc) · 23.4 KB

setup.md

File metadata and controls

387 lines (260 loc) · 23.4 KB

Setup Guides

Before running any setup, make sure you have completed the one-time steps:

  1. Clone and install — see the root README.

  2. Environment variables — create .env as described in Environment variables.

  3. Which account to run as — identity is an application account (users.id), not a Telegram chat id. For the local harnesses (make deploy-wallet, make agent) set:

    APP_USER_ID=1

    deploy_wallet.resolve_harness_user reads it, and smart_wallet_agent.main uses the same resolution. If APP_USER_ID is unset it falls back to translating TELEGRAM_CHAT_ID through the users table — which is what keeps a pre-2026-09-10 setup working, since the migration created an account for your old chat id and recorded it as users.telegram_chat_id. A raw chat id is never used as a key any more, so an unlinked one is an error rather than a silent new account.

    Telegram is optional. Only the Telegram bot (make bot) needs a bot from @BotFather — one bot for every network, with groups and inline mode turned off — and a real Telegram account. Its token goes in MITFAH_API and its username (no @) in MITFAH_USERNAME; each user picks the network inside the chat with /chain. The bot serves only private chats that have been linked to an account through the web app's deep link (POST /api/integrations/telegram/link); an unlinked chat is refused. Its /web command links the web app: set WEB_APP_URL (e.g. https://mitfah.com), or it uses the first of CORS_ORIGINS. Coming from the five per-network bots? See Moving from five Telegram bots to one. For the interactive CLI (make agent), skip all of it and just set APP_USER_ID.

  4. Web API secrets — make api needs JWT_SECRET (any long random string; python -c "import secrets;print(secrets.token_hex(32))"). The bot's username (MITFAH_USERNAME) is needed only to mint the Telegram deep link and to show the bot in the web app, and CORS_ORIGINS (comma-separated, default http://localhost:3000) must list your front end since the refresh cookie requires credentialed CORS. Set COOKIE_SECURE=0 for local http development. SIWE_DOMAIN (comma-separated, default localhost:3000) is the site a sign-in message must name — set it to mitfah.com in production; a message written for any other site is refused, which is what stops a phishing page from replaying a victim's signature to sign in as them. Signing in with the wallet (SIWE) is the only way in: there is no email, password or Google sign-in.

    The LLM provider is optional too. The agent defaults to DeepSeek (DEEPSEEK_API_KEY); swap in any other LangChain chat model with a small edit to app/smart_wallet_agent.py (see docs/app.md) and that key is no longer required.


The Setup Sequence

Every network (Anvil, Ethereum mainnet fork, Sepolia fork, live Sepolia, BSC fork, live BSC) follows the same five steps. The sections below give the exact commands for each network — this is the shape of what's happening and why the order matters:

  1. Start the network — make anvil, make mainnet-fork, make sepolia-fork, make bsc-fork, make arb-fork or make base-fork. This must be running before anything else, since every later step talks to it over RPC. (Live deployments skip this — there's no local node to start.)

    Each fork has its own port, so several can run at once: sepolia-fork 8545, bsc-fork 8546, mainnet-fork 8547, arbitrum-fork (make arb-fork) 8548, base-fork 8549. Plain anvil and celo-fork use 8545 too. The fork targets pass --port to Anvil themselves, and fund/deploy pick the port from ARGS, so the commands are the same as before — just always pass ARGS. The ports live in two places that must agree: the Makefile's FORK_PORT_* variables and the *-fork rows of RPCS in app/seed_data.py (re-run make db after changing them). Their chain IDs differ, so one API process with APP_FORK_MODE=1 serves every running fork. In your browser wallet, point each network at its fork's port.

    Starting a fork also funds the bundlers. As soon as the fork answers, the target gives every bundler key (API_BUNDLER_n, TELEGRAM_BUNDLER_n) 100 of the chain's native coin each (the same anvil_setBalance as make fund). If that port already has a node on it, the target refuses to start instead.

  2. Deploy the shared protocol — make deploy ARGS="<network>". Runs script/DeploySHProtocol.s.sol, which deploys the infrastructure shared by all users: EntryPoint, mocks (on Anvil), SHOracle, SHTreasury, SHRegistry, SpendingLimitModule, and SHFactory (with the module set via the factory constructor). Only needs to be re-run when this infra doesn't exist yet on the target chain (e.g. after restarting Anvil, which wipes all chain state).

  3. Sync the database — make db. Seeds reference data (token addresses, selectors, RPC URLs, Chainlink feed addresses) and, critically, reads the SHFactory address out of the Forge broadcast file written in step 2 (broadcast/DeploySHProtocol.s.sol/<chain_id>/run-latest.json) into the factory table. This step must run after step 2 — deploy_wallet.py resolves the factory address from the DB, not from the broadcast file directly. Safe to re-run any time; it's idempotent.

    Watch for stale addresses. Every re-run of forge script script/DeploySHProtocol.s.sol --broadcast on the same chain overwrites that chain's run-latest.json with a fresh set of addresses (new nonces → different addresses for every contract, including SHFactory). If you deploy again without re-running make db, the DB keeps pointing at the old (now-wrong) addresses — this can fail in a confusing way, since the old address might still have some contract's bytecode at it (e.g. a previous deployment's SpendingLimitModule), producing an empty-revert rather than an obvious "no code" error.

  4. Deploy your wallet — make deploy-wallet ARGS="<network>". Calls SHFactory.deployWallet(dailyLimitUsd, windowDuration, watchedTokens, sessionKeyX, sessionKeyY, sessionKeyValidUntil, trustedSpenders) to create a per-user SessionHandler in one transaction: SpendingLimitModule is installed as a spending-cap hook seeded with a $50k/24h cap over the network's default watched tokens, the harness's passkey is approved for 30 days, and the chain's exchange contracts are granted as trusted spenders (constants.get_default_spenders: Uniswap V3's SwapRouter02 and position manager on Ethereum, Sepolia, Arbitrum and Base, PancakeSwap's V2 router on BSC, none on bare Anvil). It is funded with ETH by the same call (deployWallet is payable).

    One transaction, not three. addSession and addTrustedSpender used to be separate owner-signed follow-ups. They are now seeded inside initialize, so deploy_wallet.py's add_default_session() / trust_default_spenders() are recovery helpers only and are no longer called by __main__.

    The harness's passkey is a software one. A real user's passkey is made on their phone or laptop, in the web app. The command line has no browser, so deploy_wallet.py approves a P-256 key held in software instead (harness_passkey), derived from the deployer's private key, the user and the chain. It adds no power — whoever holds that private key already owns the wallet — and lets make agent, make e2e-test and make agent-smoke approve quotes the way a phone would.

    Re-running gives you a NEW wallet, not the same one. The CREATE2 salt is derived from (deployer, factory.deployCount[deployer]) and the factory consumes that counter on every deploy, so each make deploy-wallet lands on a fresh address with no salt bookkeeping on the Python side. It does not migrate anything: session_handlers is overwritten for that chat_id, and the previous wallet keeps its prefund, tokens and LP positions with nothing pointing at it. The script warns when it is about to do this — withdraw first if the old wallet holds anything you want.

    ARGS here must exactly match the network you started in step 1 and deployed to in step 2 ("anvil", "mainnet-fork", "sepolia-fork", "sepolia", "bsc-fork", or "bsc") — deploy_wallet.py's deploy() dispatcher passes it straight through to network. If it doesn't match, the script will look up the wrong factory address (or none at all) and fail. Omitting ARGS defaults to "anvil", matching make deploy's own no-ARGS default.

  5. Start talking to it — make bot (Telegram) or make agent (interactive CLI). In the CLI, a quote prints as a card; type yes to approve it with the harness's passkey. In Telegram, a quote's "Confirm with passkey" opens the web app's approval page, so the bot needs WEB_APP_URL and a passkey made in the web app.

    The Telegram bot follows APP_FORK_MODE, like the API. With APP_FORK_MODE=1 every network it offers in /chain is that network's local fork. A network that isn't running (or answers as another chain) when the bot starts is left out until the next start, with a message saying why — so on forks, start the forks you want first. Plain Anvil is never offered.

Shortcut — make setup-agent ARGS="<network>". Runs steps 2, 3, 4 and 5 (deploy → fund → db → deploy-wallet → agent) in one command, stopping if any step fails, then drops straight into the interactive CLI agent at the end. fund here is a balance top-up via anvil_setBalance (see the Makefile's "Funding Wallets" section) — it automatically skips itself on live networks (sepolia/bsc), where there's no local Anvil node to fund, so this chain is safe to use for all six networks without special-casing. Step 1 (start the network) is not part of this chain — start the network first (skip this for live deployments). The same ARGS value is passed through to deploy, fund, and deploy-wallet internally, so it must be one of the network names listed in step 4 above.

The full local setup in two commands:

make <network>-fork    # or `make anvil` for plain local Anvil — see step 1 above
make setup-agent ARGS="<network>"

e.g. make sepolia-fork followed by make setup-agent ARGS="sepolia-fork". For a live deployment (no local node to start), just run make setup-agent ARGS="sepolia" (or "bsc") on its own.

Two siblings run the identical chain and differ only in what they leave you in: make setup-bot ARGS="<network>" ends in the Telegram bot instead of the CLI agent, and make setup-test ARGS="<network>" stops after deploy-wallet with no front end, for running tests against a freshly deployed stack.

Celo is not yet supported end-to-end. The Python app layer has scaffolding for it (token list, chain ID, network routing), but HelperConfig.s.sol has no Celo chain ID branch, so step 2 (make deploy ARGS="celo-fork" or similar) cannot succeed on Celo until that's added on the Solidity side. Note also that wrap_eth is meaningless on Celo — CELO is natively an ERC-20 with no wrapping step. See docs/app.md for what's already wired up.


Local Setup (Anvil)

Step 1 — Start Anvil:

make anvil

Step 2 — Deploy the shared protocol:

make deploy

(No ARGS needed — the default NETWORK_ARGS in the Makefile already points at http://127.0.0.1:8545 with the Anvil default account.) This deploys the full mock stack (EntryPoint, ERC20Mocks, MockV3Aggregators, MockIdentityRegistry, MockReputationRegistry, SHOracle, SHTreasury, SHRegistry, SpendingLimitModule, SHFactory).

Step 3 — Sync the database:

make db

Step 4 — Deploy your wallet:

make deploy-wallet

(No ARGS needed — deploy_wallet.py defaults to "anvil" when none is given, matching make deploy's own default. Pass ARGS="anvil" explicitly if you'd rather not rely on the default.)

Must be re-run (along with steps 1, 2 and 3) whenever Anvil is restarted — chain state, including the deployed protocol, is wiped on restart.

Shortcut — full setup in two commands:

make anvil
make setup-agent

Runs steps 2, 3 and 4 above plus a balance top-up (fund), finishing with the interactive CLI agent below. No ARGS needed for either command — both default to plain Anvil. See The Setup Sequence above.

Step 5 — No Telegram here. The Telegram bot serves Ethereum, Sepolia, BNB Smart Chain, Arbitrum One and Base (live or forked), not plain Anvil, so it has nothing to offer a wallet on Anvil. Talk to the agent through the interactive CLI below instead.

Step 6 — Chat:

What is my wallet address?
What is my USDC balance?
Send 10 LINK to Sandy

Optional — interactive CLI (no Telegram required):

make agent

Local Setup (Mainnet Fork)

Requires MAINNET_RPC_URL in .env.

Step 1 — Start a mainnet fork:

make mainnet-fork

Step 2 — Deploy the shared protocol:

make deploy ARGS="mainnet-fork"

ARGS="mainnet-fork" sends to the mainnet fork's port (8547) and signs with SEPOLIA_ACCOUNT / API_BUNDLER_1. Without it, make deploy targets plain Anvil on 8545 with the Anvil burner key.

Step 3 — Sync the database:

make db

Step 4 — Deploy your wallet:

make deploy-wallet ARGS="mainnet-fork"

Step 5 — Start:

make bot      # Telegram bot
make agent    # Interactive CLI

Re-run steps 1, 2 and 3 after any Anvil restart — fork state is wiped on restart.

Shortcut — full setup in two commands:

make mainnet-fork
make setup-agent ARGS="mainnet-fork"

Runs steps 2, 3 and 4 above plus a balance top-up (fund), finishing with make agent (not make bot) from step 5. See The Setup Sequence above.


Local Setup (Sepolia Fork)

Requires SEPOLIA_RPC_URL in .env.

Step 1 — Start a Sepolia fork:

make sepolia-fork

Step 2 — Deploy the shared protocol:

make deploy ARGS="sepolia-fork"

ARGS="sepolia-fork" tells the Makefile to sign with SEPOLIA_ACCOUNT / API_BUNDLER_1 while still broadcasting to the local fork at http://127.0.0.1:8545.

Step 3 — Sync the database:

make db

Step 4 — Deploy your wallet:

make deploy-wallet ARGS="sepolia-fork"

Step 5 — Start:

make bot
make agent

Uniswap V3 is officially deployed on Sepolia, so the swap and liquidity-position tools work here. The wallet is seeded with a $50k/24h USD cap over the default Sepolia watched tokens (WETH, USDC, LINK) and the harness's passkey, which can drive any of them — see docs/app.md.

Shortcut — full setup in two commands:

make sepolia-fork
make setup-agent ARGS="sepolia-fork"

Runs steps 2, 3 and 4 above plus a balance top-up (fund), finishing with make agent (not make bot) from step 5. See The Setup Sequence above.


Local Setup (BSC Fork)

Requires BSC_RPC_URL in .env.

Step 1 — Start a BSC fork:

make bsc-fork

Step 2 — Deploy the shared protocol:

make deploy ARGS="bsc-fork"

ARGS="bsc-fork" signs with MAINNET_DEPLOYER_PK (the placeholder deployer key baked into HelperConfig.s.sol) and broadcasts with --legacy --skip-simulation — BSC's fee-history data and this fork's baseFeePerGas: 0 blocks both confuse Forge's default gas estimation, so the Makefile routes around it (see the comment above the bsc-fork branch in Makefile for the full explanation). If the deployer needs a balance bump on the fork first, make fund ARGS="bsc-fork" sets it directly via anvil_setBalance.

Step 3 — Sync the database:

make db

Step 4 — Deploy your wallet:

make deploy-wallet ARGS="bsc-fork"

Seeds the wallet with a $50k/24h USD cap over the default BSC watched tokens (WBNB, USDC, USDT) and the harness's passkey — see docs/app.md.

Step 5 — Start:

make bot
make agent

Shortcut — full setup in two commands:

make bsc-fork
make setup-agent ARGS="bsc-fork"

Runs steps 2, 3 and 4 above plus a balance top-up (fund), finishing with make agent (not make bot) from step 5. See The Setup Sequence above.


Sepolia Deployment (Live)

Requires SEPOLIA_RPC_URL (Alchemy endpoint), and every API_BUNDLER_n and TELEGRAM_BUNDLER_n key funded with Sepolia ETH — API_BUNDLER_1 deploys, the API's keys bundle for the API, the Telegram ones for the bot.

There's no local node to start for a live deployment, so this flow skips step 1 above.

Step 1 — Deploy the shared protocol to live Sepolia:

make deploy ARGS="sepolia"

ARGS="sepolia" broadcasts to the real SEPOLIA_RPC_URL and (if ETHERSCAN_API_KEY is set) verifies contracts on Etherscan.

Step 2 — Sync the database:

make db

Step 3 — Deploy your wallet:

make deploy-wallet ARGS="sepolia"

On Sepolia, as everywhere, the app is its own bundler: bundler.py submits UserOps inside handleOps from one of the API_BUNDLER_n keys (the API) or the TELEGRAM_BUNDLER_n keys (the bot), which the EntryPoint repays out of each wallet's prefund. Nothing tops those keys up on a live chain, so keep them funded. If ETHERSCAN_API_KEY is set, both SHOracle and SessionHandler are automatically verified on Etherscan after deployment.

Uniswap V3 is officially deployed on Sepolia, so the swap, quote and liquidity-position tools are available here too. The wallet is seeded with a $50k/24h USD cap over WETH/USDC/LINK and the harness's passkey, which can drive any external call within that cap.

Step 4 — Start:

make bot
make agent

Shortcut — full setup in one command:

make setup-agent ARGS="sepolia"

Since there's no local node to start for a live deployment, this alone covers steps 1, 2 and 3 above, finishing with make agent (not make bot) from step 4. The chain's fund step (a local-fork-only balance top-up) automatically no-ops on live networks, so it's safe to include here. This broadcasts a real transaction to live Sepolia — make sure that's what you intend before running it.

Deployed Contracts (Sepolia)

Contract Address
IdentityRegistry (canonical ERC-8004) 0x8004A818BFB912233c491871b3d84c89A494BD9e
ReputationRegistry (canonical ERC-8004) 0x8004B663056A597Dffe9eCcC1965A193B7388713

SessionHandler, SpendingLimitModule, and SHOracle addresses are deployment-specific and intentionally omitted here — they change on every fresh forge script --broadcast run (different deployer nonces). After running make deploy ARGS="sepolia", the fresh addresses are in the Forge broadcast file (broadcast/DeploySHProtocol.s.sol/11155111/run-latest.json) and get synced into wallet.db by make db.


BSC Deployment (Live)

Requires BSC_RPC_URL (Alchemy endpoint) and MAINNET_DEPLOYER_PK/MAINNET_DEPLOYER_ADDRESS (see Makefile) funded with real BNB, plus BSC_PRIVATE_KEY for the Python wallet-deployment step.

There's no local node to start for a live deployment, so this flow skips step 1 above.

Step 1 — Deploy the shared protocol to live BSC:

make deploy ARGS="bsc"

The Makefile's ARGS matching for BSC only has an explicit branch for bsc-fork (which broadcasts to a local fork with --legacy --skip-simulation). A plain ARGS="bsc" falls through to the generic NETWORK_ARGS default (Anvil's burner key), which is not what you want for a real broadcast — replace NETWORK_ARGS with a BSC-specific branch (mirroring the sepolia branch: --rpc-url $(BSC_RPC_URL) --private-key <funded-bsc-key> --broadcast) before deploying live. This is a real gap in the current Makefile, not a documentation oversight — double-check the resolved command before broadcasting.

Step 2 — Sync the database:

make db

Step 3 — Deploy your wallet:

make deploy-wallet ARGS="bsc"

BSC is self-bundled like every other network: bundler.py signs handleOps with one of the API_BUNDLER_n or TELEGRAM_BUNDLER_n keys, depending on which process is running (see bundler.pick_bundler). On bsc-fork make fund tops them all up; on live BSC they must hold real BNB. Live BSC has not been exercised yet.

Step 4 — Start:

make bot
make agent

Shortcut — full setup in one command:

make setup-agent ARGS="bsc"

Since there's no local node to start for a live deployment, this alone covers steps 1, 2 and 3 above, finishing with make agent (not make bot) from step 4. The chain's fund step (a local-fork-only balance top-up) automatically no-ops on live networks, so it's safe to include here. This broadcasts a real transaction to live BSC — make sure that's what you intend before running it. Also note the make deploy gap flagged in step 1 above: confirm NETWORK_ARGS actually resolves to a funded BSC broadcast before relying on this shortcut for a live BSC deploy.


Moving from five Telegram bots to one

Until 2026-10-16 each network had a Telegram bot of its own. Now one bot serves every network, and each user picks the network inside the chat with /chain. To move an installation that ran the five bots:

  1. Make one bot in @BotFather. Turn groups off (/setjoingroups → Disable) and leave inline mode off (a new bot starts with it off). The bot answers only private chats either way; this stops it being added to a group at all.
  2. Update .env. Set MITFAH_API to the new bot's token and MITFAH_USERNAME to its username (no @). Delete the ten old variables: MITFAH_ETH_API, MITFAH_SEPOLIA_API, MITFAH_BSC_API, MITFAH_ARB_API, MITFAH_BASE_API and the five matching …_USERNAMEs. Nothing reads them any more; if MITFAH_API is missing while some are still set, make bot names them. TELEGRAM_MAX_CONCURRENT (how many Telegram chat turns run at once, default 64) is optional.
  3. Run make db before restarting anything. It adds users.telegram_chain_id, where each account's /chain choice is kept (migration 6). The bot refuses to start without it, and the API writes it too: unlinking Telegram clears it.
  4. Restart make api and make bot (still exactly one copy of the bot). The API now gives the bot's username in /api/me (telegram_bot) instead of per network in /api/chains, and its link endpoint takes no chain. Deploy the matching web build with it: an older one looks for the bots in /api/chains and would offer no Telegram link.
  5. Users press Start in the new bot. Their link carries over: in a private chat a user's chat id is the same in every bot, so the chat id already stored for their account (users.telegram_chat_id) still matches and nobody links again. Until they press Start the bot may not write to them, so they get no daily checks. Each network's conversation is still there and resumes when the user picks that network with /chain.
  6. Retire the five old bots. Nothing answers in them once make bot runs with the new token. Point their users to the new bot (a last message, or each old bot's description in BotFather) before deleting them.