depositoor

Accept any token on any chain. Settle in USDC. One address, non-custodial, powered by EIP-7702.

depositoor

Created At

ETHGlobal Cannes 2026

Project Description

depositoor is a non-custodial deposit sweeper that lets users pay with any ERC-20 token or native ETH on any supported chain (Ethereum, Arbitrum, Base, Optimism, Polygon) and automatically converts it to USDC delivered to any address on any chain. It solves a fundamental pain point in crypto payments: the user wants to pay with whatever they have, the merchant wants USDC on one chain.

The core mechanism uses EIP-7702 account delegation. For each deposit session, a fresh EOA keypair is generated client-side. The user's browser signs an EIP-7702 authorization delegating that EOA to our DepositoorDelegate contract (built on Solady's ERC-7821). The user then sends any token to this plain Ethereum address — no smart wallet deployment, no counterfactual addresses, no setup transaction. A backend relayer detects the deposit, sets the delegation, and executes batched calls (approve, swap, bridge) through the delegated account in a single EIP-7702 type 4 transaction. The user never signs anything beyond the initial token transfer.

Token conversions route through the Uniswap Trading API using the proxy approval flow (x-permit2-disabled header). This gives us a simple approve+call pattern that works natively inside ERC-7821 batches — no Permit2 signatures, no off-chain EIP-712 signing from the burner. We discovered that setting the swapper field to the destination address in the Uniswap quote encodes the output recipient directly in the calldata, while the proxy contract still pulls input tokens from msg.sender (the burner). This lets us deliver swap output directly to the destination in a single transaction for same-chain deposits.

Cross-chain settlements use Uniswap's BRIDGE routing, which is powered by Across Protocol under the hood. For cross-chain deposits where the input token isn't USDC, the sweeper executes two transactions: first a swap to USDC on the source chain, then a bridge call that delivers USDC to the destination on the target chain. The executor polls balanceOf between transactions to get the exact post-slippage amount.

The indexer follows chain heads via WebSocket subscription (newHeads) and fetches Transfer logs per block via HTTP RPC. This hybrid approach is more reliable than pure WebSocket log subscriptions, which silently drop under high event volume on chains like Base. The indexer also polls eth_getBalance for pending burner addresses to detect native ETH deposits. When ETH is detected, the sweeper prepends a WETH.deposit() call to the batch before swapping.

The DepositoorDelegate contract is approximately 50 lines of Solidity. It implements ERC-7821 batch execution authorized by an immutable keeper address, a sweep() function that transfers the entire balance of any token to a recipient (solving the problem of not knowing exact post-swap amounts at batch construction time), and a receive() function that auto-wraps incoming ETH to WETH. The contract is deployed at the same address (0x33333393A5EdE0c5E257b836034b8ab48078f53c) across all five supported chains via CREATE3.

The project ships as a React SDK (@depositoor/react) with two exports: DepositoorProvider and DepositWidget. Developers wrap their app with the provider, drop in the widget with a destination address and chain ID, and the entire flow — burner generation, EIP-7702 auth signing, session registration, QR code display, real-time status via SSE, swap execution, cross-chain bridging — is handled internally. The frontend also supports wallet connectivity via Reown AppKit across EVM chains and Solana, with adapters for Porto, Coinbase Smart Wallet, and WalletConnect QR code sessions for mobile wallets.

The backend is written in Rust using Axum and Alloy, with PostgreSQL for session persistence and advisory locks for safe multi-instance coordination. It runs as three process types — API server, indexer (one per chain), and sweeper (one per chain) — totaling 11 processes in production across 5 chains. A refund endpoint allows returning tokens to users when swaps fail due to unsupported tokens or insufficient liquidity.

How it's Made

The backend is Rust with Axum for the HTTP server and Alloy for all Ethereum interactions. We chose Rust because the sweeper needs to construct complex EIP-7702 type 4 transactions with authorization lists, ERC-7821 batch-encoded calldata, and precise gas management across five chains simultaneously. Alloy's sol! macro lets us define contract interfaces inline and encode calldata without ABIs or code generation.

The smart contract is built on Solady's ERC-7821 implementation — a gas-optimized minimal batch executor. Our DepositoorDelegate adds only three things on top: a keeper authorization check, a sweep() function for balance-based transfers, and a receive() fallback that wraps ETH to WETH. We deliberately kept all swap and bridge logic off-chain in the sweeper so we can upgrade routing without redeploying contracts. The contract was deployed to the same address on all five chains using CREATE3.

The Uniswap Trading API integration required solving a non-obvious problem: their default flow uses Permit2 for token authorization, which requires EIP-712 signatures from the token holder. Our burner is an EIP-7702 delegated EOA controlled by the relayer — it can execute on-chain calls but can't sign off-chain typed data. We discovered the x-permit2-disabled header, which switches to a proxy approval flow where a wrapper contract pulls tokens via transferFrom(msg.sender). Since msg.sender inside our ERC-7821 batch is the burner, a standard ERC-20 approve in the same batch is all we need. No Permit2, no signatures.

The most notable hack is the "swapper trick" for same-chain deposits. The Uniswap API doesn't have a documented recipient field for CLASSIC routing — output always goes to the swapper address. But the proxy contract pulls input tokens from msg.sender, not from the swapper encoded in the calldata. So we set swapper to the destination address when getting the quote. The proxy pulls tokens from the burner (msg.sender), executes the swap, and the output goes directly to the destination. Single transaction, no intermediate balance reads.

For cross-chain bridging, we use Uniswap's BRIDGE routing type which is powered by Across Protocol. The same proxy approval pattern works — approve USDC to the bridge contract, call it, done. For deposits that aren't USDC, we execute two transactions: a swap on the source chain (USDC stays at the burner), then read the exact post-slippage balance with a polling loop, then bridge. We tried Circle Gateway initially for cross-chain but hit a ~20 minute deposit finality requirement that was unacceptable for the UX.

The indexer uses a hybrid WebSocket + HTTP approach we developed after discovering that pure WebSocket log subscriptions silently drop events on both public and paid RPC providers under Base's event volume. We subscribe to newHeads via WebSocket for block notifications, then fetch Transfer logs for each block via HTTP eth_getLogs. This combination is rock solid. The indexer also polls eth_getBalance for pending burner addresses to detect native ETH deposits, since ETH transfers don't emit ERC-20 Transfer events.

The frontend wallet connectivity uses Reown AppKit (WalletConnect) for multi-chain support. We built adapters for EIP-6963 injected wallets, Porto (Ithaca's EIP-7702 smart wallet), Coinbase Smart Wallet, WalletConnect QR sessions, and Solana wallets via the Wallet Standard. All are normalized to a single WalletProviderDetail interface. The SDK (@depositoor/react) packages the entire deposit flow into two React components — a provider and a widget — so integration is about 10 lines of code.

Session state is managed through PostgreSQL with LISTEN/NOTIFY for real-time SSE updates and advisory locks (pg_advisory_xact_lock) for safe multi-instance coordination across indexers and sweepers. Failed sessions automatically re-activate when a new deposit hits the same burner address, and a refund endpoint lets the relayer return tokens via the sweep() contract function when swaps fail.

background image mobile

Join the mailing list

Get the latest news and updates

depositoor | ETHGlobal