Not a hook.
A second leg.
The obvious way to build this is a Uniswap v4 hook that skims every swap in the pool. It is also the wrong way, and not for a subtle reason: a hook only ever sees the pools that were created with it. A DOSE hook would dose your trades in DOSE's own pools — which is not where anyone trades, and is the exact opposite of “every swap you make.”
So DOSE does not sit inside a pool at all. It composes the dose one level up, where the transaction is assembled: your trade routes to the deepest liquidity that exists, and a second leg carries the dose to the asset you are building. One call, one signature, both or neither.
Both legs are priced before you commit
You pick the trade. DOSE prices it against the deepest pool on the chain, then prices the dose against that output — the asset you are building — in the same pass. Two numbers, one screen, nothing signed yet.
What it cannot do
matters more.
It cannot move your price
Leg one is the swap you would have made anyway, routed to whatever pool on the chain is deepest for that pair. DOSE owns no pool and never asks you to trade in one, so there is no version of this where using it costs you execution.
It cannot hold your money
There is no DOSE contract. Nothing is deployed, nothing is upgradeable, and no address anywhere has a claim on your balance — the protocol is a transaction that your own wallet signs and the public router executes.
It cannot half-execute
Both legs are commands in the same call. If the dose leg cannot fill, your trade does not fill either. There is no state in which you paid for a trade and got no dose, and none in which a dose is owed to you later.
It cannot pretend to be exact
The dose is sized on the quoted output of leg one, not the realised fill, so it lands within your slippage tolerance of the exact percentage rather than on it. And it only applies to swaps made here. Both of those are real limits, and the honest fix for the first is a small periphery contract — which does not exist yet.
One function.
This is the whole protocol. Not a summary of it — the thing itself. There is no contract to deploy, no pool to migrate into, and no privileged address, because DOSE's entire job is to write a transaction that you then sign.
Which means the 500 bps ceiling is a product decision, not bytecode. Nobody can raise it without shipping a build, but nobody is stopped by a contract either. If that ever needs to be a guarantee rather than a promise, it is a hundred-line periphery contract — and this page will say so.
/**
* One transaction, two legs. The first is the trade you asked
* for; the second is the dose, sized off the first one's quote.
*/
export async function planDosedSwap(
trade: { from: Currency; to: Currency; amountIn: bigint },
dose: { target: Currency; bps: number },
account: Address,
) {
// Leg 1 — routed to the deepest pool on the chain for this
// pair. DOSE owns no pool and never routes you into one.
const legOne = await route(trade.from, trade.to, trade.amountIn);
// Leg 2 — the dose, taken from what leg 1 is quoted to return.
// Sized on the quote, so it lands within slippage of exact.
const cut = (legOne.quotedOut * BigInt(dose.bps)) / 10_000n;
if (trade.to === dose.target || cut === 0n) return legOne.plan;
const legTwo = await route(trade.to, dose.target, cut);
return plan()
.swapExactIn(legOne.path, trade.amountIn, minOut(legOne))
.swapExactIn(legTwo.path, cut, minOut(legTwo))
.takeAll(trade.to, account)
.takeAll(dose.target, account)
.encode(); // one router call, one signature, atomic