Add HyperSwap AMM connector for HyperEVM#641
Conversation
fengtality
left a comment
There was a problem hiding this comment.
Thanks for the connector — a few blocking issues from my read:
🔴 Critical (must fix before merge)
1. Router ABI contains only multicall — every swap/liquidity call will throw at runtime.
src/connectors/hyperswap/hyperswap_v2_router_abi.json ships with only a multicall function. Every route that uses IHyperswapV2Router02ABI — swapExactTokensForTokens, swapTokensForExactTokens, addLiquidity, addLiquidityETH, removeLiquidity, removeLiquidityETH — will resolve to undefined on the ethers Contract and throw TypeError: routerContract.swapExactTokensForTokens is not a function. The connector is non-functional as shipped. Please include the full Uniswap-V2 Router02 ABI.
2. addLiquidity.ts:207-218 — addLiquidityETH called with msg.value even when base token is WETH.
When baseTokenObj.symbol === 'WETH', the code still routes through addLiquidityETH with value: rawBaseTokenAmount. WETH is an ERC-20, not native ETH — this burns native gas without depositing WETH and will likely revert (router would try to wrap already-wrapped tokens). The native-ETH path only makes sense for unwrapped ETH; WETH should use addLiquidity (both ERC-20s).
3. removeLiquidity.ts:1909-1910 — hardcoded 0.5% slippage ignores user config.
const slippageTolerance = new Percent(5, 1000); // 0.5%slippagePct from the request is accepted but never applied. Users who configure higher slippage will get unexpected reverts.
4. executeSwap.ts:758 — quoteToken || '' silent fallback.
quoteToken is Type.Optional(...) in the schema, so it can be undefined. The || '' fallback then calls getToken('') which returns null, surfacing as a confusing notFound error instead of 'quoteToken is required'. Validate at the route handler.
🟡 Important
hyperswap.utils.ts:2374-2383—formatTokenAmountcatches errors and returns0. This violates the repo's no-fallback rule (CLAUDE.md). A silent zero will corrupt slippage / allowance / fee math downstream. Remove the try/catch.schemas.ts:2486-2487—AMM_POOL_ADDRESS_EXAMPLEis commented "WETH-USDC pool on Base";CLMM_POOL_ADDRESS_EXAMPLEreferences BSC.BASE_TOKEN/QUOTE_TOKENdefaults areUSDT/WBNB— tokens that don't exist in the HyperEVM token list. Swagger examples should reference real HyperEVM pools/tokens.schemas.ts— ~200 LoC ofHyperswapClmm*andHyperswapExecuteQuote*schemas are defined but never wired to a registered route. Drop them or scope the PR to include the routes.hyperswap.config.ts:2042—|| 4fallback onmaximumHopsshould be removed (the YAML template already provides the value; let a misconfiguration surface).- Multiple route files —
require('@fastify/sensible')is re-registered per-route via CommonJSrequire()even thoughhyperswap.routes.tsalready registers it at the wrapper level. Remove the duplicates.
🟢 Nits
hyperswap.utils.ts:findPoolAddressis exported but always returnsnulland never called — dead code.hyperswap.ts:getV2Poolsilently returnsnullon any error including RPC failures — should re-throw non-expected errors.positionInfo.ts:972hardcodesdefault: 'base'fornetwork— should be'hyperevm'.- 63 LoC of tests for ~2700 LoC is well below the project's 75% coverage target.
Verdict
Needs rework before merge. The router ABI + WETH addLiquidityETH path are blocking — neither swaps nor liquidity operations work as-is. The slippage hardcode and quoteToken silent fallback are also user-facing bugs. Happy to re-review once these are addressed.
|
Thanks for the detailed review. I pushed an update addressing the requested changes:
Validation run locally: Results: typecheck passed, build passed, scoped ESLint had 0 errors, and the HyperSwap route test suite passed: 4/4 tests. |
fengtality
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround — all four critical items from my first review are genuinely fixed (router ABI, WETH addLiquidityETH path, remove-liquidity slippage, quoteToken validation), along with most of the important ones. 👍
However, I verified the connector against HyperEVM mainnet (chainId 999) directly, and there are new blocking issues — the connector has never been exercised against the live chain.
🔴 Critical (must fix before merge)
1. The contract addresses have no code on HyperEVM mainnet.
eth_getCode returns 0x for the router (0xda0f518d521e0dE83fAdC8500C2D21b6a6C39bF9), the factory (0x4df039804873717bff7d03694fb941cf0469b79e), and the example pool in schemas.ts. The live deployment is:
- Factory:
0x724412C00059bf7d6ee7d4a1d0D5cd4de3ea1C48 - V2Router02:
0xb4a9C4e6Ea8E2191d2FA5B380452a634Fb21240A
(verified on-chain: router.factory() returns that factory, router.WETH() returns WHYPE 0x5555...5555). The docs page cited in hyperswap.contracts.ts appears to be stale. Note hyperswap.routes.test.ts currently pins the dead addresses, so the test enshrines the bug.
2. Gateway crashes at startup — hyperswap namespace is never registered.
There is no $namespace hyperswap entry in src/templates/root.yml and no src/templates/namespace/hyperswap-schema.json. ConfigManagerV2.get() throws for an unregistered namespace (config-manager-v2.ts), and hyperswap.config.ts calls get('hyperswap.slippagePct') at module load, which app.ts imports — so the server won't boot at all. The test suite passes only because test/mocks/shared-mocks.ts replaces ConfigManagerV2 with a plain map that returns undefined instead of throwing. Please add the namespace schema + root.yml registration (see pancakeswap for the pattern) and run pnpm start unmocked to verify.
3. HyperEVM network wiring is incomplete — and to be explicit: HyperEVM should be an Ethereum network, not a chain.
The template placement (chains/ethereum/hyperevm.yml, tokens/ethereum/hyperevm.json) is right, but the network is never registered: root.yml needs $namespace ethereum-hyperevm → chains/ethereum/hyperevm.yml + ethereum-network-schema.json, exactly like ethereum-base / ethereum-arbitrum. Without it, Ethereum.getInstance('hyperevm') throws. Please also add templates/pools/ethereum/hyperevm.json so the network follows the per-network pool-template convention.
4. Swap quotes use the wrong fee — 0.25% instead of HyperSwap's 0.30%.
quoteSwap.ts builds trades with @pancakeswap/v2-sdk, whose Pair math hardcodes PancakeSwap's 0.25% fee (9975/10000). I compared the live router's getAmountsOut against pool reserves on the real WHYPE/USDT0 pair: on-chain output matches the 0.30% (997/1000) model exactly. As-is, every quote overestimates output, misreports price to strategies, and silently eats into the slippage buffer.
Question on this: is @pancakeswap/sdk the appropriate dependency here at all? HyperSwap is a Uniswap V2 fork, and the PancakeSwap SDK bakes in both the 0.25% fee and PancakeSwap's factory/init-code-hash for pair-address derivation. @uniswap/v2-sdk is already a project dependency (used by the uniswap connector) and carries the correct 997/1000 fee constants — is there a reason to prefer the PancakeSwap SDK, or was this inherited from copying the pancakeswap connector? I'd like to hear the rationale; otherwise please switch to @uniswap/v2-sdk (or explicit constant-product math).
🟡 Important
addLiquidity/removeLiquiditytreat pooltoken0as base unconditionally (getV2PoolInfomaps token0→base, token1→quote). A user'sbaseTokenAmountcan silently apply to the wrong token depending on address sort order. Other connectors resolve base/quote ordering from curated pool templates via PoolService —findDefaultPoolhere bypasses PoolService entirely (factorygetPairlookup), which also breaks thePOST /poolsflow.- Native-token handling was copied without adaptation. The
baseToken === 'ETH'wrap path inaddLiquidity.tsis unreachable on HyperEVM (native is HYPE, wrapped is WHYPE), and error messages say "Insufficient ETH balance" where gas is HYPE. Either support HYPE→WHYPE wrapping or remove the dead code. - Token list has a single entry (WHYPE) — there is no tradeable pair by symbol out of the box. Please add the major HyperEVM tokens (USD₮0, UETH, UBTC, …).
- Schema examples still reference WETH/USDC, which don't exist in the HyperEVM token list, and the example pool address is one of the dead addresses above.
🟢 Nits (carried over from the first review, still open)
hyperswap.utils.tsfindPoolAddressis dead code (always returnsnull).getV2Pool/getV2PoolInfostill catch all errors and returnnull— RPC failures surface as "Pool not found".positionInfo.tsstill defaultsnetworkto'base'with WETH/USDC examples.- Coverage is still ~63 LoC of structural assertions for ~2,400 LoC of connector code — and one of them pins the wrong contract addresses.
Verdict
Requesting changes again. Items 1–4 mean the connector cannot start or reach the chain as shipped. Before the next push, please validate end-to-end against HyperEVM mainnet with an unmocked server (pnpm start, then quote-swap on a real WHYPE pair) — that would have caught all four.
Summary
/connectors/hyperswap/ammin Gateway and advertises it from/config/connectors.Testing
corepack pnpm exec jest --runInBand test/connectors/hyperswap/hyperswap.routes.test.tscorepack pnpm run buildcorepack pnpm exec eslint "src/connectors/hyperswap/**/*.ts" "test/connectors/hyperswap/**/*.ts" --format table(0 errors; existing default-import/no-unused warnings only)Closes #503