Agentic wallet infrastructure.
SessionHandler Protocol gives every user a programmable smart account and lets an AI agent run it for them across DeFi — swapping, paying, providing liquidity and building on-chain reputation — from plain-language instructions. The user keeps the root key. The agent prepares each transaction, and the user approves it with a passkey on their own device — Face ID, a fingerprint or a PIN. And the protocol, not the agent, decides what is allowed to happen.
It is built from the standards the ecosystem is converging on:
- ERC-4337 account abstraction — every agent action is a UserOperation through the canonical EntryPoint
- ERC-7579 modular accounts — behaviour is added by installing modules, not by rewriting the account
- ERC-8004 agent identity and reputation — the protocol's agent is a registered on-chain identity that users can review
- Chainlink price feeds — every limit and fee is expressed in US dollars, on every chain
The reference application is a web app in web/: sign in with a browser wallet, deploy a wallet with one signature, set its rules, and talk to the assistant on the web or in Telegram.
The protocol is built in layers: a small on-chain core that enforces the rules, shared contracts that configure it, integrations with DeFi, token and identity protocols, a runtime for the agent, and the applications people actually use.
┌─────────────────────────────────────────────────────────────────────────────┐
│ APPLICATIONS web app · Telegram bot · CLI agent │
├─────────────────────────────────────────────────────────────────────────────┤
│ SERVICE LAYER FastAPI · accounts, wallet sign-in (SIWE) │
│ wallet-signed contacts · owner actions in-browser │
├─────────────────────────────────────────────────────────────────────────────┤
│ AGENT RUNTIME LangChain agent (DeepSeek by default, any chat model) │
│ the account is injected — the model can't choose it │
├─────────────────────────────────────────────────────────────────────────────┤
│ INTEGRATIONS ERC-20 · Uniswap V3 / PancakeSwap V2 · ERC-8004 │
│ Chainlink pricing · UserOp builder and bundler │
├─────────────────────────────────────────────────────────────────────────────┤
│ KEY CUSTODY passkeys on users' devices — the server holds none │
├─────────────────────────────────────────────────────────────────────────────┤
│ CORE (on-chain) SessionHandler account + SpendingLimitModule hook │
│ every agent transaction is checked here │
├─────────────────────────────────────────────────────────────────────────────┤
│ SHARED CONTRACTS SHTreasury · SHRegistry · SHOracle · SHFactory │
├─────────────────────────────────────────────────────────────────────────────┤
│ CHAINS Ethereum · Arbitrum · Base · BNB Chain │
│ Sepolia · Anvil │
└─────────────────────────────────────────────────────────────────────────────┘
SessionHandler is each user's ERC-7579 smart account, one per user per chain. It checks its own UserOperations and accepts a signature from either the owner or one of up to five approved session keys. A session key is a passkey, one per device: the device or the user's password manager keeps it, never the server, and every signature needs Face ID, a fingerprint or the device PIN, checked on-chain through the chain's P-256 precompile. A session key can move value but can never touch the account's own settings, so nothing signed with one can loosen the wallet's rules — and it expires, so a key nobody renews stops working on its own rather than living forever.
Every transaction the account executes is wrapped by SpendingLimitModule, an ERC-7579 hook. It measures how much US-dollar value left the wallet (native coin plus the watched tokens, before vs. after) and refuses anything that would go past the owner's limit for the current window. Because it measures the result rather than reading calldata, it works the same for a transfer, a swap on any venue, or a liquidity move: a fair swap costs almost nothing against the limit, a bad-rate or draining swap costs what was actually lost. It also forbids standing token approvals — an approval must be used up within the same transaction, unless it goes to a spender the owner trusts (like the DEX router).
The account also caps what session keys can spend on gas, which the hook cannot see: a ceiling per operation and a budget per day across all of them, both set per chain at deploy and adjustable by the owner.
This hook is what turns "an AI can move my money" into "at most $X per day can leave my wallet, whatever goes wrong". Everything above it in the stack can fail — a confused model, a bad prompt, a compromised server — and the most that can leave the wallet is still bounded here. It is a standalone module, so any ERC-7579 account can install it.
Owners also keep full control of the wallet: pause the wallet, remove any device's key, change the limit and window, choose which tokens count, trust spenders, cap network fees, and withdraw.
| Contract | Role |
|---|---|
SHTreasury |
The admin root. Owns the registry, oracle and factory, and collects protocol fees. |
SHRegistry |
Central settings every wallet reads live: EntryPoint, ERC-8004 registries, oracle, fee, treasury, agent id, and the module new wallets install. A change here reaches every wallet with no redeploy. Swapping the oracle takes a 2-day timelock. |
SHOracle |
Turns token amounts into US dollars with Chainlink feeds. Refuses stale prices, and on L2s refuses to price while the sequencer is down. |
SHFactory |
Deploys wallets with CREATE2, so the address is known before the transaction. One signature creates the wallet, installs the hook with its limit, authorizes the first device's passkey, trusts the chain's exchange contracts and funds it. |
SHTreasury (operator — admin root, fee sink)
├── SHRegistry ── live settings for every wallet
│ └── SHOracle ── Chainlink USD prices (2-day timelocked swap)
└── SHFactory ── deployWallet(...) in one transaction
└── SessionHandler (per user, per chain)
├── validates owner + passkey signatures
├── guards its own admin surface from session keys
├── pays a small flat protocol fee per agent action
└── hook ──▶ SpendingLimitModule ──▶ SHOracle
Protocol economics. Each agent-driven execution pays a flat fee in the chain's native coin: about $0.015 at launch, and always within bounds set at deploy to about $0.005–$0.10. Owner actions are free. See docs/contracts.md.
"Swap $200 of ETH for USDC"
│
▼ app (web / Telegram / CLI) → API → agent runtime
│ agent checks remaining budget, gets a quote, runs a preflight
▼ integrations build [approve, swap, approve 0] as one batch
▼ the user taps Confirm and approves with their passkey (Face ID / fingerprint / PIN),
│ which signs that exact UserOp on their own device
▼ ERC-4337 EntryPoint → SessionHandler.validateUserOp
▼ SpendingLimitModule.preCheck → snapshot the wallet's USD value
▼ the swap runs on Uniswap / PancakeSwap
▼ SpendingLimitModule.postCheck → value lost ≤ remaining limit? else revert
▼ receipt back to the user in plain language
The agent has 43 tools across four areas (each network shows it only its own exchange's liquidity tools):
- Payments — native and ERC-20 transfers to contacts the owner has saved on the web, each one signed by the owner's wallet (the contact list is the destination allowlist; the agent can read it, never edit it)
- DeFi trading and liquidity — quotes, one swap tool for every swap shape, Uniswap V3 liquidity positions (price ranges to pick from, deposits, fee collection, withdrawals) and PancakeSwap V2 liquidity on BNB Chain, wrapping, with the wallet's checks run before any write
- Budget awareness — remaining limit, whether a planned spend fits, live USD prices
- Identity and reputation (ERC-8004) — look up agents, read and give feedback, resolve registration files; the protocol's own agent is registered on-chain and user wallets act as its reviewers
The LLM is DeepSeek (deepseek-flash) by default; any LangChain chat model can be swapped in with a small edit to app/smart_wallet_agent.py.
The owner key never leaves the user's browser wallet — deploying and every owner action are signed there, so the service is non-custodial. Session keys are passkeys on the user's own devices, so the server holds no key that can move funds, and every agent transaction waits for the user's Face ID, fingerprint or PIN on that exact operation. The agent is handed the user's account by the runtime and cannot pick another. And every agent transaction, no matter which layer produced it, passes through the spending-limit hook on-chain, with its gas capped per operation and per day. API keys are masked in every error, log line and tool result before they leave the server. Full analysis: THREAT_MODEL.md.
| Network | Chain ID | DEX | Status |
|---|---|---|---|
| Ethereum mainnet | 1 | Uniswap V3 | fork-tested |
| Arbitrum One | 42161 | Uniswap V3 | fork-tested, with L2 sequencer check |
| Base | 8453 | Uniswap V3 | fork-tested, with L2 sequencer check |
| BNB Chain | 56 | PancakeSwap V2 | fork-tested, live deploy path |
| Sepolia | 11155111 | Uniswap V3 | live testnet + fork |
| Anvil | 31337 | mocks | local |
Celo has partial scaffolding in the Python layer but no contract deployment path yet. New chains are added with the add-network skill in .claude/skills/.
- Foundry
- Python 3.12+
- Node 22.22+ — only for the web app in
web/ - An LLM API key —
DEEPSEEK_API_KEYby default, or any other LangChain chat model - An Alchemy API key — for live Sepolia, the mainnet / BSC / Arbitrum / Base forks, and the History tab on Base
- (Optional) A Telegram bot from @BotFather — one bot serves every network — only for
make bot
git clone https://github.com/Conrad-sudo/sh-protocol.git
cd sh-protocol
# Contracts
forge install
# Python back end and agent
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Web app (optional)
cd web && npm installmake sepolia-fork # terminal 1: local fork with real Chainlink feeds
make setup-agent ARGS="sepolia-fork" # terminal 2: deploy protocol → db → wallet → CLI agentTo run the full app instead: COOKIE_SECURE=0 make api and cd web && npm run dev, then open http://localhost:3000. Every network and step is explained in docs/setup.md.
forge test --no-match-path "test/fork/*" # unit, guard, admin and invariant suites
make py-test # the seventeen offline Python suites (no chain, no model)
make e2e-test # full user journey against a running fork (Sepolia; ARGS=arbitrum-fork, ARGS=base-fork, ...)
make agent-smoke # a real agent conversation against the fork
cd web && npm test && npm run e2eCI (.github/workflows/test.yml) checks the smart contracts only, on every push: the Foundry suites with contract sizes and Slither (failing on any high-impact finding). The fork suites run nightly when their RPC secrets (MAINNET_RPC_URL, SEPOLIA_RPC_URL, BSC_RPC_URL, ARB_RPC_URL, BASE_RPC_URL) are set. The Python and web suites run locally (see the Makefile and web/package.json).
deploy/Caddyfile runs the whole service on one server: HTTPS, security headers (a Content-Security-Policy among them, kept in step with the web app by web/src/securityHeaders.test.ts), the built web app, and /api passed to the API started with make api-prod. Its header comment lists what to set first. Before real money goes in, the protocol's admin key must be separate from the API's (THREAT_MODEL.md §4.4), and WEB_APP_URL must be the site's public https address, since passkeys are bound to that domain for life (docs/environment.md).
| Document | Contents |
|---|---|
| docs/contracts.md | Every contract, the spending-limit design, the fee, the test suite |
| docs/app.md | Agent runtime, integrations, bundler, API and Telegram bot |
| web/README.md | The web app |
| docs/environment.md | Environment variables |
| docs/setup.md | Local, fork and live deployments |
| docs/langchain-packages-migration.md | Why ERC-20 / Uniswap calldata comes from external packages |
| docs/reference.md | Makefile reference, project structure, dependencies |
| THREAT_MODEL.md | Assets, trust boundaries, on-chain and off-chain threats |
Business Source License 1.1 until 2029-06-12, after which it becomes MIT. The
interfaces in src/interfaces/ and the test mocks in src/mocks/ stay MIT (some are adapted from
Chainlink and OpenZeppelin), as do the tests and deploy scripts.
