RageQuit Escrow is a payment safety layer for autonomous agents. It lets an agent prepare and queue a payment, but final settlement is delayed by an onchain veto window controlled by the human operator.
The project is built around one principle: agentic payments should be observable and revocable before they become irreversible.
Autonomous agents are increasingly able to reason about tasks, choose recipients, and trigger financial actions. That is useful, but the failure mode is harsh: a single wrong transfer can be final.
RageQuit Escrow changes the execution model. Instead of granting an agent direct transfer authority, the agent can only create a pending payment. The human receives visibility through the dashboard and Telegram, and the contract enforces a bounded window where the human can cancel the payment.
This gives the system three practical safety properties:
- Delay before finality: payments are queued first and can only settle after
unlocksAt. - Human last word: the owner can veto while the window is open.
- Auditable intent: each payment carries an
intentHash, optionalfundingReference, and decision events.
flowchart LR
user["Human operator"]
agent["Agent runner"]
risk["Private risk gate"]
escrow["RageQuitEscrow contract"]
watcher["Telegram watcher"]
ui["Operator dashboard"]
keeper["Keeper"]
recipient["Recipient"]
artifacts["agent.json and agent_log.json"]
user --> ui
agent --> risk
risk -->|allowed| escrow
risk -->|blocked| artifacts
escrow --> watcher
escrow --> ui
watcher --> user
ui -->|veto| escrow
watcher -->|veto command| escrow
keeper -->|execute after timeout| escrow
escrow --> recipient
escrow --> artifacts
The core contract is intentionally small. Most surrounding code exists to make the payment lifecycle visible, testable, and easy to operate from local development through testnets.
sequenceDiagram
participant Agent
participant Risk as Risk gate
participant Escrow as RageQuitEscrow
participant Human
participant Keeper
participant Recipient
Agent->>Risk: Build payment intent
Risk-->>Agent: queue_payment or block_payment
alt Risk blocks payment
Agent->>Agent: Record blocked run log
else Risk allows payment
Agent->>Escrow: initiate(...) or initiateWithFundingReference(...)
Escrow-->>Human: PaymentQueued event
Human->>Escrow: Optional veto(paymentId)
alt Vetoed inside window
Escrow-->>Human: PaymentVetoed event
else Window expires
Keeper->>Escrow: execute(paymentId)
Escrow->>Recipient: Transfer native/token funds
Escrow-->>Keeper: PaymentExecuted event
end
end
The contract recognizes three decisions:
Queued: an authorized agent locked funds into a pending payment.Vetoed: the owner cancelled the payment beforeunlocksAt.Executed: the veto window closed and the payment settled.
The escrow contract lives at contracts/contracts/RageQuitEscrow.sol.
Key roles:
owner: can veto payments and update configuration.authorizedAgent: can queue payments.keeper: any account can callexecuteonce a payment is mature.
Key limits:
vetoWindow: delay before execution is allowed.spendLimit: maximum amount for one payment.settlementToken: zero address for native settlement, or ERC-20 address for token settlement.
Core functions:
fund(): owner funds native-mode escrow.fundToken(amount): owner funds token-mode escrow.initiate(recipient, amount, intentHash): authorized agent queues a native or token payment.initiateWithFundingReference(recipient, amount, intentHash, fundingReference): queues with an external funding or swap reference.veto(paymentId): owner cancels during the veto window.execute(paymentId): settles after the veto window.canExecute(paymentId): view helper for keepers and UIs.
flowchart TB
subgraph Offchain["Offchain agent layer"]
task["Task input"]
reasoning["Reasoning provider\nlocal or OpenAI"]
risk["Risk provider\nlocal or Venice"]
runner["Agent runner"]
end
subgraph Onchain["Onchain enforcement"]
escrow["Escrow balance"]
pending["PendingPayment state"]
veto["Owner veto"]
execute["Timeout execution"]
end
subgraph Observability["Operator visibility"]
dashboard["Dashboard"]
telegram["Telegram alerts"]
logs["Run logs and artifacts"]
end
task --> reasoning --> risk --> runner
runner --> pending
pending --> escrow
pending --> dashboard
pending --> telegram
pending --> logs
veto --> pending
execute --> escrow
The offchain agent can recommend and queue a payment, but cannot bypass the contract checks. The contract enforces spend limits, escrow balance, agent authorization, veto timing, and final settlement.
- contracts: Hardhat workspace for Solidity contracts, tests, deploy scripts, agent runner, keeper, Telegram watcher, and generated state.
- frontend: Next.js operator dashboard.
- agent.json: generated agent metadata.
- agent_log.json: generated audit log.
Important scripts:
- deploy.js: deploys escrow and optional local mocks.
- agentRunner.js: builds payment intents, runs risk checks, queues payments, and records run logs.
- keeper.js: executes mature payments.
- telegramWatcher.js: sends Telegram alerts, edits countdowns, and handles Telegram veto commands.
- generateAgentArtifacts.js: writes
agent.jsonandagent_log.json.
- Native and ERC-20 escrow settlement
- Owner-controlled veto window
- Spend limit per payment
- Private risk gate before queueing
- Telegram alerts with countdowns and veto commands
- Dashboard veto flow
- Keeper-based post-timeout execution
- Local mock token and mock swap router
- Optional Uniswap V3-style swap path configuration
- MetaMask smart-account and delegation helpers
- Celo Alfajores network support
- ENS, Self, and payment-rail metadata in generated artifacts
Install dependencies:
npm installCopy environment files:
copy contracts\.env.example contracts\.env
copy frontend\.env.local.example frontend\.env.localRun tests:
npm run contracts:testStart a local chain:
npm run contracts:nodeDeploy locally:
npm run contracts:deploy:localAfter deployment, set NEXT_PUBLIC_ESCROW_ADDRESS in frontend/.env.local from contracts/deployments/localhost.json.
Run the frontend:
npm run frontend:devSet the task, recipient, and amount in contracts/.env:
AGENT_TASK=Pay vendor invoice 42
AGENT_RECIPIENT=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
AGENT_AMOUNT_ETH=0.1Queue a payment locally:
npm run contracts:run-agent:localThe runner writes entries to contracts/runs/localhost.json. If AGENT_REFRESH_ARTIFACTS=true, it also refreshes agent.json and agent_log.json.
Configure Telegram in contracts/.env:
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
TELEGRAM_VETO_ENABLED=true
WATCHER_TIMER_INTERVAL_SECONDS=15Start the watcher:
npm run contracts:watch:telegram:localThe watcher supports:
- queued payment alerts
- countdown message edits
- inline veto button
/vetofor the latest active payment/veto <paymentId>for an explicit payment
For public networks, set TELEGRAM_VETO_PRIVATE_KEY to an owner key if the owner account is not available through the Hardhat signer list. Do not commit this value.
Run one keeper pass:
KEEPER_ONCE=true npm run contracts:keeperThe keeper scans recent payment IDs, skips vetoed or already executed payments, and calls execute(paymentId) for payments whose veto window has closed.
For token-settled local runs, configure:
SETTLEMENT_MODE=token
LOCAL_DEPLOY_MOCK_TOKEN=true
INITIAL_FUND_TOKEN_UNITS=2000000000
AGENT_AMOUNT_WEI=125000000Deploy again and run the agent. The local deploy script creates a mock ERC-20 and funds the escrow with token units instead of native value.
The agent runner can fund the escrow before queueing a token payment:
SETTLEMENT_MODE=token
AGENT_FUNDING_MODE=swap-native
AGENT_SWAP_NATIVE_AMOUNT_ETH=0.2
AGENT_AMOUNT_WEI=125000000Localhost uses the mock swap router when SWAP_ROUTER_KIND=mock. Public networks can be configured for Uniswap V3-style routing with:
SWAP_ROUTER_KIND=uniswap-v3
SWAP_ROUTER_ADDRESS=
UNISWAP_WRAPPED_NATIVE_TOKEN=
UNISWAP_QUOTER_ADDRESS=
UNISWAP_POOL_FEE=3000
UNISWAP_SLIPPAGE_BPS=500The resulting run log records swap/funding metadata and the fundingReference stored onchain.
Sepolia:
npm run contracts:deploy:sepolia
npm run contracts:run-agent:sepolia
npm run contracts:watch:telegram:sepolia
npm run contracts:artifacts:sepoliaAlfajores:
npm run contracts:deploy:alfajores
npm run contracts:run-agent:alfajores
npm run contracts:watch:telegram:alfajores
npm run contracts:artifacts:alfajoresRequired environment variables vary by network. See contracts/.env.example for RPC URLs, private keys, settlement settings, risk providers, and metadata options.
RageQuit Escrow maintains two layers of audit data:
- Run logs in
contracts/runs/<network>.json - Agent artifacts in
agent.json,agent_log.json, andfrontend/public/
Refresh artifacts manually:
npm run contracts:artifacts:localThe frontend also includes a refresh endpoint used by the dashboard audit panel.
- Keep private keys, Telegram bot tokens, and API keys out of git.
- The authorized agent can only queue payments; it cannot veto or update owner configuration.
- The owner can veto but cannot execute early.
- The keeper can execute mature payments but cannot bypass the veto window.
- Risk checks are a pre-queue guardrail, not a substitute for contract-level controls.
- Token settlement depends on the behavior of the configured ERC-20.
Primary checks:
npm run contracts:test
npm run frontend:buildThe contract tests cover queueing, authorization, spend limits, veto timing, native execution, token execution, and funding-mode validation.