Skip to content
Protocol contracts not yet configured for this build
Enter app

Pons V2 integration

Post-graduation routing

Once a launch reaches phase 2, PLOUTO trades in a canonical Uniswap v4 pool. Buybacks must route there instead of the curve.

No Universal Router

At the time of writing there is no canonically documented Universal Router deployment on Robinhood Chain. Inventing an address, or guessing at one from another chain, would be unsafe — a wrong router address is a lost buyback at best.

So Plouto does not use one. BuybackExecutor settles directly against the verified Uniswap v4 PoolManager at 0x8366a39CC670B4001A1121B8F6A443A643e40951, read from factory.poolManager() and verified on Blockscout.

Reconstructing the PoolKey

The pool is identified by a PoolKey, rebuilt from the factory's own launch record:

solidity
function poolKeyFor(address token) public view returns (PoolKey memory key) {    PonsLaunchedToken memory record = ponsFactory.getLaunchedToken(token);    if (!record.exists) revert RouteMismatch();    if (record.pairToken != address(0)) revert RouteMismatch();     // Native ETH is address(0) and therefore always currency0.    key = PoolKey({        currency0: address(0),        currency1: record.token,        fee: record.poolFee,        tickSpacing: record.tickSpacing,        hooks: ponsMemeHook    });}

Nothing here is hardcoded except the hook address, which is itself read from factory.memeHook() at construction.

A fork test confirms the reconstructed key resolves to a pool the PoolManager has actually initialized.

The swap

Uniswap v4 uses a lock-and-settle model. The executor implements IUnlockCallback and does the whole exchange inside one callback:

solidity
function unlockCallback(bytes calldata data) external override returns (bytes memory) {    if (msg.sender != address(poolManager)) revert NotPoolManager();    (PoolKey memory key, uint256 ethIn, uint256 minTokensOut) = abi.decode(data, (PoolKey, uint256, uint256));     int256 delta = poolManager.swap(        key,        SwapParams({            zeroForOne: true,            amountSpecified: -int256(ethIn),          // negative == exact input            sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1        }),        ""    );     int128 delta0 = int128(delta >> 128);    int128 delta1 = int128(delta);     uint256 owed     = delta0 < 0 ? uint256(uint128(-delta0)) : 0;    uint256 received = delta1 > 0 ? uint256(uint128(delta1))  : 0;    if (received < minTokensOut) revert InsufficientOutput(received, minTokensOut);     if (owed != 0)     poolManager.settle{value: owed}();    if (received != 0) poolManager.take(key.currency1, address(this), received);     return abi.encode(received);}

unlockCallback is callable only by the PoolManager. Anyone else gets NotPoolManager.

Price impact, bounded on chain

The executor reads the pool's sqrtPriceX96 straight out of PoolManager storage rather than trusting a quote:

solidity
function poolSqrtPriceX96(address token) public view returns (uint160) {    bytes32 stateSlot = keccak256(abi.encode(poolId(token), POOLS_SLOT));    bytes32 slot0 = poolManager.extsload(stateSlot);    return uint160(uint256(slot0));}

That spot price gives an ideal output, and the realised output must land within maxPriceImpactBps of it:

solidity
uint256 idealOut = Math.mulDiv(Math.mulDiv(ethIn, sqrtPriceX96, Q96), sqrtPriceX96, Q96);uint256 impactFloor = Math.mulDiv(idealOut, BPS - maxPriceImpactBps, BPS);

The two mulDiv steps avoid overflowing sqrtPrice².

Verified against a live pool

A fork test reconstructs the PoolKey for a real graduated Pons launch, confirms the pool is initialized, and executes a 0.005 ETH swap through it — receiving and retiring tokens against genuine liquidity.