
EnsPay
Pay anyone. Just their name.
Demo
About
The problem it solves # What Can You Use ENSPay For? --- ## Freelancers and Creators Getting Paid in Crypto Instead of sending clients a long wallet address and explaining "make sure it's on Base, and send USDC not ETH" you just share **yourname.eth**. Your client types it in, enters the amount, and pays. ENSPay handles the token and chain automatically. No back-and-forth, no wrong-chain mistakes. > **Easier because:** You set your preferences once. Every future payment just works. --- ## Merchants and Indie Sellers Accepting Crypto Put your ENS QR code at checkout, physical or digital. Customers scan it exactly like a UPI code. ENSPay resolves your preferences and routes payment to you in your preferred token on your preferred chain. > **Easier because:** No payment terminal setup, no chain-specific wallets to manage. One QR does everything. --- ## Splitting Bills and Sending Money to Friends Splitting dinner? Instead of fumbling with wallet addresses, your friend scans your QR from the /receive page and sends their share. Works even if they're holding ETH and you want USDC. ENSPay swaps on the way. > **Easier because:** Token mismatch is no longer a problem. You get what you want, they send what they have. --- ## Private Payments Without Extra Steps If you're a journalist, activist, or just someone who values financial privacy, turn on **stealth mode**. Every payment you receive goes to a fresh one-time address. No one on-chain can link payments back to your identity. > **Safer because:** Your wallet history stays private without requiring the payer to do anything differently. They just scan and pay. --- ## Cross-Border and Cross-Chain Payments Sending money across chains today means manually bridging, waiting, and hoping nothing breaks. ENSPay integrates **Across Protocol v3** to handle this automatically. The payer pays on their chain; the receiver gets it on theirs. > **Easier because:** Cross-chain complexity is completely abstracted away. Neither party needs to understand bridging. --- ## DAOs and Teams Paying Contributors A DAO can set a shared ENS name with team payment preferences. Contributors submit work and get paid to the ENS name. No need to collect wallet addresses from every contributor each cycle, and no risk of paying to the wrong chain. > **Easier because:** One ENS name, one set of preferences, consistent payouts every time. --- ## Shareable Payment Links Every ENS name gets a dedicated URL like `/pay/alice.eth`. Share it in your Twitter bio, Discord profile, invoice email, or README. Anyone who clicks it lands on a clean payment page, ready to pay. > **Easier because:** It works like a payment link (think PayPal.me) but fully on-chain, non-custodial, and chain-agnostic. --- ## TL;DR — The Core Value | Old Way | ENSPay Way | |---|---| | Share wallet address + specify chain + specify token | Share your .eth name | | Payer sends wrong token — funds stuck | ENSPay swaps automatically | | Payer on wrong chain — manual bridge | ENSPay bridges automatically | | Wallet address is public and traceable | Stealth mode gives a fresh address every time | | Payment links require custodial services | `/pay/alice.eth` is fully on-chain | ENSPay does not reinvent crypto payments. It makes them feel like they should have felt all along.
How it's made
Challenges we ran into ## Setting ENS Preferences: From Six Transactions to One The first version of the preference setup sent six separate setText() transactions on the ENS PublicResolver, one for each key (enspay.token, enspay.network, enspay.dex, enspay.slippage, enspay.note, enspay.stealth). That was a terrible experience. The user had to confirm six wallet popups in a row, pay gas six times, and wait for each one to land before the next could fire. If they rejected or one failed midway, their preferences were left in a half-written state on-chain. The fix was multicall() on the PublicResolver. ENS's resolver supports batching multiple setText() calls into a single transaction. I encoded each call as ABI-encoded calldata and passed them as an array. One wallet confirmation, one gas payment, all six records written atomically. If it fails, nothing is written. The UX went from painful to seamless overnight. > The key insight was treating the six records not as six separate writes but as a single atomic preference object that either saves completely or not at all. --- ## Uniswap V3 Integration: Getting the Swap Router Right Integrating Uniswap V3 inside the smart contract had several sharp edges. The first issue was using the wrong router address. Base Sepolia uses SwapRouter02 at a specific address, not the same one as mainnet or other testnets. Transactions were silently reverting until I traced the issue down to the router address being wrong. The second issue was amountOutMinimum. Setting it to zero made swaps pass in testing but is dangerous in production. Setting it too tight caused legitimate swaps to revert due to price movement between quote and execution. The fix was building applyDefaultSlippage() into the contract itself, applying a 0.5% (50 bps) tolerance to the quoted output, giving a realistic minimum without being exploitable. The third issue was token approval flow. The contract needs the payer to approve it as a spender before calling resolveAndSwap(). Getting the frontend to sequence the approval transaction and then the swap transaction correctly, and handling the case where approval was already sufficient, took several iterations to get right. --- ## Across Protocol: No Hardcoded Addresses The initial approach to cross-chain bridging hardcoded the SpokePool address for each chain. That broke immediately because Across v3 returns the correct spokePoolAddress dynamically from their quotes API at app.across.to/api/suggested-fees. The fix was to never hardcode SpokePool addresses. Every cross-chain payment now starts with a getBridgeQuote() call that fetches the fee suggestion and extracts the spokePoolAddress from the response. The frontend then calls depositV3() on that address directly. This also meant the integration is future-proof. If Across upgrades or migrates their SpokePool, the app adapts automatically without a contract redeployment. > One hard limitation: Across Protocol only operates on mainnet chains. On testnets, cross-chain attempts return an error. All the bridging logic is fully wired and tested against mainnet parameters, but live end-to-end bridging is a mainnet-only flow.