
Slope
Split a big swap into non-custodial scheduled slices along a curve you choose
Demo
About
Existing on-chain execution limits large taker orders to two patterns, and both carry cost. A single market transaction absorbs the entire price impact at once, while manual slicing reintroduces the coordination problem the split was meant to solve, one approval and one transaction at a time, and scheduled alternatives run on a fixed clock regardless of market conditions. Slope introduces a taker-side execution position in which a single order is itself a bounded, executable time curve. A user declares a budget, a duration, and a pace shape (aggressive, neutral, or conservative), plus hard price rails, and the curve determines how much of the budget is authorised at each point in time. A flat pace reduces to ordinary linear TWAP, so the conventional schedule is a special case rather than a separate mode. The schedule lives on-chain: the SlopePosition contract computes how much each slice may sell, measures price impact with a dual-quote probe, enforces the price rails, and pulls tokens per fill with no escrow, so an unexecuted budget never leaves the owner's wallet, and a terminal clamp settles the exact remainder after the window closes. Execution is delegated through a Privy session signer scoped to exactly what the position declared: the contract address, the function selector, the position id, a per-transaction cap, and an expiry. The keeper triggers fills repeatedly without further approval and can never exceed what the schedule authorises; the contract enforces the budget and the schedule, and the Privy policy enforces the scope. The system refuses to act when conditions are wrong: prices outside the declared band, impact above the limit, or an emptied allowance are each recorded on-chain with their reason, and the keeper reads that history back from The Graph to decide whether to retry or park. Realised execution is measured against a linear-TWAP benchmark computed in the subgraph, so the value of adapting to market conditions is a number users can verify. The live application supports creating schedules from the browser, an execution staircase that fills in real time, per-fill deviation against the benchmark in basis points, an Aqua protocol trace decoded per fill with @1inch/aqua-sdk, and a CSV export of the full event stream, all on Base Sepolia.
How it's made
Slope is a TypeScript and Solidity monorepo. The onchain system uses Solidity 0.8.30 with Foundry and via-IR, and the keeper and interface are TypeScript on Node 24. A fixed-point curve kernel compiles each schedule's budget, duration, and pace shape into an authorised amount per instant. It covers the linear branch, a square-root front-loaded branch, and a squared back-loaded branch, with boundary conditions exact by construction. Custody is pull-per-fill: tokenIn is pulled from the owner at fill time and never escrowed, so a cancelled schedule needs no refund path, and a terminal clamp settles the exact remainder after the window closes so integer-division dust cannot strand a position. Price impact is measured with a dual quote on the same router in the same call: a small probe approximating spot, and the real fill size, where the difference is the fill's own footprint. Below a decimals-derived probe floor the check cannot measure anything, and the fill event records impactChecked=false rather than claiming a check that did not happen. The official 1inch Aqua and SwapVM contracts serve as the shared-liquidity and settlement layer on Base Sepolia, where no official deployment exists: the registry and SwapVM router were self-deployed from the v1.0.2 source, whose build requires via_ir with the Cancun EVM target since the function-pointer opcode table breaks without IR codegen. The shipped 1inch strategies embed a KYC gate opcode that only KYB-verified resolvers can pass, so Slope ships its own ungated strategy. Privy carries the delegation layer. A per-position P-256 keypair is registered as a 1-of-1 key quorum bound to a policy that allowlists the exact contract, the exact selector, the position id, a per-transaction cap, and an expiry matching the schedule. Cumulative spend aggregations are tracked at sign level only, so the keeper signs via eth_signTransaction and broadcasts the raw transaction itself. A policy containing an aggregation reference condition was found to reject every signing request regardless of operator or value format, established by bisecting a live policy where an identical policy without the reference passes immediately; rate limiting therefore lives in the keeper, and the budget invariant lives in Solidity where it belongs. The Graph is load-bearing in the execution path, not just the dashboard. Each tick the keeper queries the subgraph to decide what to work on: which positions are active, what is already indexed as executed, and whether a recent skip means a position should be parked. Candidate state is re-verified on-chain before signing, because indexing lag must never be the final authority on a financial decision. If the subgraph is unreachable the tick is skipped and logged; there is no fallback path and no cached substitute. The subgraph also computes a BenchmarkComparison snapshot per position, realised VWAP against the linear-TWAP counterfactual, which powers the per-fill deviation in bps shown in the interface. The application runs no database of its own: the subgraph, queried exclusively through its GraphQL endpoint, is the single structured data source for both the keeper's decision-making and the frontend, and the only local persistence is a JSON file holding per-position delegated signing keys. The interface uses React, Vite, viem, and Tailwind, with every curve drawn on the Canvas API rather than a charting library, so the time ruler, the planned-versus-actual divergence, and the fill and skip marks stamped along the axis are one consistent instrument. Email onboarding runs through Privy embedded wallets, an Aqua protocol trace is decoded per fill with @1inch/aqua-sdk, and an awaiting-index state covers the window while the subgraph catches up with a fresh schedule. The frontend is hosted on Vercel, the subgraph on The Graph Studio, and the keeper runs under systemd on a VPS. Testing includes Foundry unit, integration, and fuzz suites with simulated time, cross-validation against an independent TypeScript reference model through committed vectors spanning all three pace shapes across five durations including asymmetric-decimal price normalisation, and Vitest coverage on the keeper and shared model.