Build with Signal
v4 quote and trade
A Signal v4 pool is an ordinary Uniswap v4 pool with a hook, so there are two ways to trade it: through Signal's router, or through any router that follows the PoolManager's rules. Keep every amount in integer base units and every identity verified as in discovery.
Direction
| Trade | Input | Output | zeroForOne |
|---|---|---|---|
| Buy | Pair asset | Token | true when the pair asset is currency0, that is !terms.tokenIs0 |
| Sale | Token | Pair asset | terms.tokenIs0 |
Swaps are exact input only. There is no exact-output path.
Quote
With Uniswap's Quoter. Call quoteExactInputSingle on the Quoter in the deployment record with the pool key, zeroForOne, the input amount and empty hookData, as an eth_call:
struct QuoteExactSingleParams { PoolKey poolKey; bool zeroForOne; uint128 exactAmount; bytes hookData; }
function quoteExactInputSingle(QuoteExactSingleParams params)
returns (uint256 amountOut, uint256 gasEstimate);The Quoter runs the swap with the hook in it, so amountOut is already net of every hook fee in force at that block: pool fee, tax, opening fee, size and deviation extras. Checked on 12 October 2026 against a live 4.2 pool.
Two limits:
- The Quoter is not one of Signal's approved routers. While a pool's Signal-only window or wallet cap runs, a buy quote through it reverts with
GatedorWhaleCap. The revert data wraps the hook's error; look for its selector. - It models the pool, not the pair asset. If the pair asset keeps a cut of its own transfers, the amount that reaches a wallet is lower. See below.
By simulating the real call. An eth_call of the router method below, from the real wallet, with the real value or allowance, returns the output the user would receive and passes every opening rule exactly as a transaction would. Use this for the number you show before a signature.
A quote of zero, or a revert, is not an executable quote. Say so instead of sending.
Trade through Signal's router
Router: 0xcf9d5f4dc9860060db528bc9722cc3d828fc807c for releases 2, 4.1 and 4.2. It holds nothing between calls, has no owner, and anyone may call it. It pays the same fee as every other caller.
// Buy with native HYPE on a pool paired with WHYPE. msg.value is the input.
function buyNative(PoolKey key, uint256 minOut, address to, address refundTo, uint256 deadline)
payable returns (uint256 out);
// Any exact-input swap in an ERC-20. The input is pulled from the caller.
function swap(PoolKey key, bool zeroForOne, uint256 amountIn, uint256 minOut,
address to, bool unwrap, uint256 deadline)
returns (uint256 out);| Trade | Call | Approval |
|---|---|---|
| Buy with HYPE, WHYPE-paired pool | buyNative with msg.value = amountIn | None |
| Buy with the pair asset | swap with zeroForOne = !tokenIs0 | The pair asset to the router |
| Sell for the pair asset | swap with zeroForOne = tokenIs0, unwrap = false | The token to the router |
| Sell for native HYPE, WHYPE-paired pool | swap with zeroForOne = tokenIs0, unwrap = true | The token to the router |
- The approval is a plain ERC-20 allowance to the router, not a Permit2 permit. Approve the amount of this trade.
toreceives the output. OnbuyNative,refundToreceives any native input the pool did not use.minOutis enforced on what was received: below it the call revertsTooLittleReceived(got, min). Compute it from a fresh quote and the user's own slippage; refuse a minimum that rounds to zero.deadlineis a unix time; past it the call revertsExpired().buyNativeon a pool that is not paired with WHYPE revertsNotNativePool(). For another pair asset, obtain that asset by any route you trust, thenswap.
The router tells the hook who the swap is for: hookData = abi.encode(uint8(1), to, msg.sender, bytes32[] proof). The hook believes this only from routers its factory approved, and uses it only to judge the wallet cap and to pay a pot. It never moves funds from either address. Because the router is approved on all three releases, a buy through it passes a Signal-only window and can satisfy a wallet cap; a buy through another router cannot while those windows run.
buyNativeListed and swapListed take an allowlist proof as a last argument. The releases since 4.1 admit no launch with an allowlist, and any older allowlist window (24 hours at most) has ended, so the plain forms are the ones to use.
Trade through another router
Outside a launch's opening rules the pools accept swaps from any router: the hook has no router allowlist for sales at any time, nor for buys once the windows have ended. A Universal Router V4_SWAP with SWAP_EXACT_IN_SINGLE, the pool key and empty hookData is the standard shape. Three reminders specific to these pools:
- exact input only;
- a buy stopped by a price limit reverts
PartialFill, so leave the price limit open and protect the trade with the minimum output; - inside a Signal-only window or a wallet cap the buy reverts. Read
terms.blocksandterms.launchTsfirst.
Simulate your exact route from the real wallet before you offer it. These docs verify quotes through the Quoter; they do not certify any third-party router.
Pair assets that tax their own transfers
Some admitted pair assets keep a cut of every transfer that has a contract on either side. Today that is PSTR (0xb9cd945141f7d99fd562a6222accde5043350ad4), which keeps 10%. A router that sends the amount it owes cannot settle such an asset, so these pools trade through a router Signal deployed for them.
How to recognise one: in live configuration the pair asset's row in v4_lane_v42.quotes[] carries tax_bps and router; a token's record carries router_kind: "taxed", quote_tax_bps, and that router as router.
Taxed-pair router: 0x2fca8081d6bff127413c91108561ff0ddc978b74.
// Native HYPE in: buys the pair asset on its own V2 pool, which pays the PoolManager; then the pool trades.
function buyNative(PoolKey key, Currency quote, uint256 minOut, address to, uint256 deadline)
payable returns (uint256 received);
// Token in, native HYPE out, through the same two steps in reverse.
function sellNative(PoolKey key, Currency quote, uint256 amountIn, uint256 minOut,
address payable to, uint256 deadline) returns (uint256 sent);
// Pair asset in or out directly.
function swap(PoolKey key, bool zeroForOne, uint256 amountIn, uint256 minOut,
address to, uint256 deadline) returns (uint256 received);quote is the pair asset's address. The router pays first and swaps what arrived, and it judges minOut on what the recipient's balance actually gained, so the minimum already accounts for the asset's cut. A HYPE buyer pays the asset's cut once on the way in; a seller pays it once on the way out. The hook's own fee is unchanged and comes on top.
Quote these pools by simulating the taxed router's call. The Uniswap Quoter's answer leaves the asset's cut out.
Before handing a request to a wallet
- Chain 999; token, pool id, pool key and hook verified at a recent block.
- The launch's windows read from
terms: opening fee, Signal-only, wallet cap, per-block budget. - The live rate shown to the user, with the base it falls to if an opening is running.
- The exact call simulated from the real wallet: target, calldata, value, allowance, recipient.
- A positive minimum output from that simulation and the user's slippage. A deadline.
- The allowance bounded to this asset and this amount, to the router you are calling and nothing else.
After submission, reconcile the transaction hash before any retry. A successful simulation does not guarantee the fill: the rate falls every second during an opening and other trades move the pool. The confirmed receipt establishes what happened.
Gas
Measured on a fork of HyperEVM at the 4.2 release: a 1 HYPE buy used about 252,000 gas, a sale about 168,000, a flush about 254,000. A launch with every rule switched on can be heavier. Estimate your own call.
A flush must be sent with a much higher limit than it ends up using: each payout needs about 405,000 gas still available when it starts, or the flush reverts PayoutStarved.