Router lets a trader who only holds the chain’s dollar buy or sell a launch quoted in any synthetic currency in one transaction: it mints the synthetic through the vault, trades on the curve, and redeems whatever the curve did not take. It also wraps plain quote-asset trades with a deadline and triggers graduation after the buy that fills a curve. It has no owner, no state between calls and never holds funds.
Source: contracts/src/Router.sol (ReentrancyGuard). registry and usdc (registry.usdc()) are immutables. Addresses: Addresses.
Dollar paths
buyWithUSDC
1
Checks
Expired when block.timestamp > deadline, ZeroAmount for usdcIn == 0. The currency is registry.get(curve.code()).2
Obtain the quote asset
usdcIn raw dollars are pulled from the caller (approve the dollar to the router first). If the quote asset is the dollar (a USD launch), quoteIn = usdcIn and the whole msg.value is refunded. If the currency is tier 2, the router computes fee = registry.oracle().updateFee(priceUpdate) (InsufficientFee if msg.value is lower), approves the vault and calls vault.mint{value: fee}(usdcIn, 0, priceUpdate); the minted synthetic amount is quoteIn, and msg.value - fee is refunded. Any other tier-1 asset (EURC) reverts UseQuoteAsset.3
Buy
The router approves the curve for
quoteIn and calls curve.buy(quoteIn, minTokensOut, msg.sender): tokens go straight to the trader, and Slippage from the curve enforces the floor. The allowance is reset to zero afterwards.4
Return what the curve did not take
The last buy before graduation is capped. Any quote left on the router is redeemed through the vault (tier 2, with an empty price update) and the dollars are sent to the trader, or transferred back as-is for a USD launch.
5
Graduate
If the curve is now
ready(), the router calls graduate() inside a try block: a failure there (for example a pool-side revert) never reverts the trade, and graduate() stays callable by anyone. BoughtWithUSDC(curve, buyer, usdcIn, quoteIn - leftover, tokensOut) is emitted.minTokensOut is the only slippage floor on this path: the vault mint runs with minSynthOut = 0, so a stale or moving oracle rate is reflected in fewer tokens rather than a separate revert. Quote with quoteBuyWithUSDC and apply your tolerance to tokensOut.
sellForUSDC
Pulls tokensIn launch tokens from the caller (approve the token to the router), sells them to the curve with minQuoteOutRaw = 0 and the router as recipient, then converts: for a USD launch usdcOut = quoteOut (full msg.value refund); for tier 2 the router pays the oracle fee and calls vault.redeem{value: fee}(quoteOut, 0, priceUpdate); EURC launches revert UseQuoteAsset. Slippage if usdcOut < minUsdcOut. The dollars are transferred to the caller and SoldForUSDC(curve, seller, tokensIn, quoteOut, usdcOut) is emitted. Selling cannot close a curve, so this path never graduates.
A redeem below 100% collateral ratio is haircut pro rata (see FxVault); minUsdcOut catches that too.
Quote-asset paths
buy pulls quoteIn of curve.quoteAsset() (approve it to the router), buys for msg.sender with minTokensOut, returns any untaken quote, and tries to graduate when the curve is ready. sell pulls the tokens, sells with minQuoteOut and pays the caller directly from the curve. Both revert Expired past the deadline and ZeroAmount for zero input. These paths work for every currency, including EUR launches quoted in EURC.
Trading directly on the curve is equivalent, minus the deadline, the refund of untaken quote and the automatic graduation.
Quotes
quoteBuyWithUSDC previews the vault mint (previewMint, so the current fresh or stale fee is included) and then curve.quoteBuy; quoteIn is the synthetic that would be minted and quoteUsed what the curve would take. It returns all zeros for a tier-1 non-dollar launch. quoteSellForUSDC previews curve.quoteSell and then previewRedeem (haircut included); usdcOut is zero for EURC launches. Both revert with the oracle’s StalePrice when the rate is older than the stale limit, since previewMint and previewRedeem call oracle.rate.
For quote-asset trades use curve.quoteBuy and curve.quoteSell directly.
Approvals, value and deadlines
With
KeeperFxOracle (every deployment today) pass priceUpdate = [] and value = 0. If the registry is ever pointed at PythFxOracle, pass the Hermes update data and value = updateFee(priceUpdate); excess value is always refunded (RefundFailed if the caller rejects it). Note that the vault forwards the fee to its own oracle: see the caveat in Vault operations.
Deadlines are unix timestamps. The app uses now + 600 seconds.
Events and errors
Curve errors (
CurveClosed, Slippage, ExceedsSold) and vault errors (StalePrice, LiabilityCapExceeded, CollateralRatioTooLow, paused) bubble up unchanged. Router.t.sol covers the synthetic and USD dollar paths, the overshoot refund with auto-graduation, the EURC revert, the quote-asset paths, expiry and slippage.