Integration Guide
HyperAMM is Helm's core liquidity-pool contract.
This guide shows the minimum contract flow for integrating HyperAMM. Examples use Viem-style calls and assume you already loaded the current ABIs and addresses.
Do not copy contract addresses from examples. Use the current address registry and verify chain ID, contract address, ABI, and paused state before every production integration.
Tools and Inputs
Tooling
The hyperamm-js repository contains two packages for integrators:
- hyperamm-sdk (
@helmfinancex/hyperamm-sdk) packages HyperAMM contract addresses, ABIs, and TypeScript helpers for swaps, LP deposits, LP withdrawals, and pool reads. - hyperamm-cli (
@helmfinancex/hyperamm-cli) provides command-line tooling backed by the SDK for scripting and operational workflows.
Use this guide as the protocol-level reference for validating what the library does under the hood: previews, approvals, slippage floors, execution fees, and post-submit tracking still matter for production integrations.
You need:
- Hyperliquid EVM RPC
- Current HyperAMM contract addresses
- Current ABIs for the contracts you call
- A wallet client for write actions
- A public client for reads and simulations
- Token decimals for every input and output token
For user-facing writes, use this order:
- Discover contracts and pool state.
- Read preview values and operation fees from RPC.
- Compute a nonzero slippage floor.
- Check ERC-20 allowances and prompt for approval if needed.
- Re-read the final preview if approval, bridge, or market state may have changed.
- Simulate the exact main transaction with the final parameters and native value.
- Prompt the wallet for the main transaction.
- Verify events and final state.
Pool Discovery
Use the Factory as the canonical registry for per-pool components.
const poolInfo = await publicClient.readContract({
address: factoryAddress,
abi: hyperAMMFactoryAbi,
functionName: "getPoolInfo",
args: [poolId],
});
const {
hyperAMM,
pool,
oracle,
hyperCoreManager,
hyperCoreTrader,
rebalancer,
executor,
withdrawer,
escrow,
reconciler,
compositionManager,
swapFeeModule,
stakingManager,
sHYPE,
} = poolInfo.deployment;
For pair-based discovery, use getPoolByPair(token0, token1, isToken0Based) and confirm the returned address is not zero.
Swap
Swaps are exact-input and execute through HyperAMMSwapRouter.
Input Specification
swap and previewLiquidityQuote both take a DirectSwapParams object.
| Field | Type | Description |
|---|---|---|
pools | address[] | Ordered list of HyperAMM pool addresses used for the route. Each pool must be registered in the router. |
isZeroToOnes | bool[] | Direction for each hop. true swaps pool token0 to token1; false swaps pool token1 to token0. Length must match pools. |
amountIns | uint256[] | Input amount for each hop. Use 0 to consume the full available balance for that hop. Length must match pools. |
isTokenOutHype | bool | If true and tokenOut is WHYPE, the router unwraps WHYPE and sends native HYPE to recipient. |
tokenIn | address | Initial input token. For native HYPE input, set this to WHYPE and send msg.value = amountIn. |
tokenOut | address | Final output token. For native HYPE output, set this to WHYPE and set isTokenOutHype = true. |
recipient | address | Output recipient. If zero address, output is sent to msg.sender. |
amountIn | uint256 | Exact input amount for the full route, in tokenIn decimals. Must be greater than zero. |
amountOutMin | uint256 | Minimum acceptable final output. Applies only to the final route output, not intermediate hops. |
deadline | uint256 | Unix timestamp. Must be current or future block time and no more than five minutes ahead. |
Route rules:
pools,isZeroToOnes, andamountInsmust have the same length.- The route must have at least one hop and cannot exceed the router's
maxHops. - The final hop output token must match
tokenOut. - Intermediate balances must be fully consumed by later hops. A route that leaves unused intermediate tokens in the router reverts.
- For a simple single-hop swap,
amountInscan be[amountIn]or[0]. - For multi-hop routes, use
0on intermediate hops when the next hop should consume all output from the previous hop.
const params = {
pools: [poolAddress],
isZeroToOnes: [true],
amountIns: [amountIn],
isTokenOutHype: false,
tokenIn,
tokenOut,
recipient: account.address,
amountIn,
amountOutMin: 0n,
deadline,
};
const [amountOut] = await publicClient.readContract({
address: swapRouterAddress,
abi: hyperAMMSwapRouterAbi,
functionName: "previewLiquidityQuote",
args: [params],
});
const amountOutMin = applySlippage(amountOut, slippageBps);
Then simulate and execute with the same route.
const finalParams = { ...params, amountOutMin };
const nativeInput = tokenIn.toLowerCase() === whypeAddress.toLowerCase() && useNativeHype;
const { request } = await publicClient.simulateContract({
account,
address: swapRouterAddress,
abi: hyperAMMSwapRouterAbi,
functionName: "swap",
args: [finalParams],
value: nativeInput ? amountIn : 0n,
});
const hash = await walletClient.writeContract(request);
Rules:
- ERC-20 input requires approval to
HyperAMMSwapRouter. - Native HYPE input uses
tokenIn = WHYPEandmsg.value = amountIn. - Native HYPE output uses
tokenOut = WHYPEandisTokenOutHype = true. - Use
amountOutMin = 0only for preview reads. The executableswaptransaction must use a nonzeroamountOutMin.
LP Deposit
LP deposits are submitted to the per-pool HyperAMMExecutor. The submit transaction creates an operation; automated execution mints the LP shares later.
const expectedShares = await publicClient.readContract({
address: lensAddress,
abi: hyperAMMLensAbi,
functionName: "previewDeposit",
args: [hyperAMMAddress, amount],
});
const executionFee = await publicClient.readContract({
address: executorAddress,
abi: hyperAMMExecutorAbi,
functionName: "executionFee",
});
const minShares = applySlippage(expectedShares, slippageBps);
const nativeDeposit = baseToken.toLowerCase() === whypeAddress.toLowerCase() && useNativeHype;
const { request } = await publicClient.simulateContract({
account,
address: executorAddress,
abi: hyperAMMExecutorAbi,
functionName: "submitDeposit",
args: [amount, minShares, deadline],
value: executionFee + (nativeDeposit ? amount : 0n),
});
const hash = await walletClient.writeContract(request);
Rules:
- ERC-20 deposits require approval to the per-pool
HyperAMMExecutor. - Native HYPE deposits into WHYPE-based pools use
msg.value = amount + executionFee. - The submit transaction is not final settlement. Track the operation until it is executed or refunded.
LP Withdraw
There are two LP withdrawal paths.
| Path | Use when | Contract call |
|---|---|---|
| Instant withdraw | EVM-side liquidity is available and the user accepts the fee | submitWithdraw or submitWithdrawNative |
| Queued withdraw | Default exit path with queue and later claim | submitQueuedWithdraw or submitQueuedWithdrawNative |
Preview queued withdraws through the Lens.
const expectedAmount = await publicClient.readContract({
address: lensAddress,
abi: hyperAMMLensAbi,
functionName: "previewQueuedWithdraw",
args: [hyperAMMAddress, shares],
});
const minAmount = applySlippage(expectedAmount, slippageBps);
Submit the request through the Executor.
const executionFee = await publicClient.readContract({
address: executorAddress,
abi: hyperAMMExecutorAbi,
functionName: "executionFee",
});
const { request } = await publicClient.simulateContract({
account,
address: executorAddress,
abi: hyperAMMExecutorAbi,
functionName: useNativeHype ? "submitQueuedWithdrawNative" : "submitQueuedWithdraw",
args: [shares, minAmount, deadline],
value: executionFee,
});
const hash = await walletClient.writeContract(request);
submitQueuedWithdraw returns an executor operation ID. It does not return the withdrawer requestId. The withdrawer request is created later when the executor processes the operation. Track the executor OperationSubmitted and QueuedWithdrawExecuted events, or read the user's request IDs from HyperAMMWithdrawer, before claiming.
const requestIds = await publicClient.readContract({
address: withdrawerAddress,
abi: hyperAMMWithdrawerAbi,
functionName: "getUserWithdrawRequests",
args: [account.address],
});
const requestId = requestIds.at(-1);
if (requestId === undefined) throw new Error("queued withdrawal request not found");
After the withdrawal request exists, claim from HyperAMMWithdrawer only when it is claimable.
const claimable = await publicClient.readContract({
address: withdrawerAddress,
abi: hyperAMMWithdrawerAbi,
functionName: "isClaimable",
args: [requestId],
});
if (!claimable) throw new Error("withdrawal is not claimable");
const { request } = await publicClient.simulateContract({
account,
address: withdrawerAddress,
abi: hyperAMMWithdrawerAbi,
functionName: "claim",
args: [requestId],
});
const hash = await walletClient.writeContract(request);
Rules:
- LP shares are ERC-20 tokens. Withdraw submit calls require LP token approval.
- Queued withdrawal amounts are determined at request time.
- A queued withdrawal may require a later claim transaction.
- If native HYPE delivery fails, check the per-pool Escrow.
LP Value
Use HyperAMM.lpValue() to read total pool value for LP display. The value is denominated in the pool's base token: token0 when isToken0Based is true, otherwise token1.
const [
totalLpValue,
totalSupply,
userShares,
isToken0Based,
token0,
token1,
token0Decimals,
token1Decimals,
] = await publicClient.multicall({
contracts: [
{
address: hyperAMMAddress,
abi: hyperAMMAbi,
functionName: "lpValue",
},
{
address: hyperAMMAddress,
abi: erc20Abi,
functionName: "totalSupply",
},
{
address: hyperAMMAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [account.address],
},
{
address: hyperAMMAddress,
abi: hyperAMMAbi,
functionName: "isToken0Based",
},
{
address: hyperAMMAddress,
abi: hyperAMMAbi,
functionName: "token0",
},
{
address: hyperAMMAddress,
abi: hyperAMMAbi,
functionName: "token1",
},
{
address: hyperAMMAddress,
abi: hyperAMMAbi,
functionName: "token0Decimals",
},
{
address: hyperAMMAddress,
abi: hyperAMMAbi,
functionName: "token1Decimals",
},
],
allowFailure: false,
});
if (totalLpValue <= 0n || totalSupply === 0n) {
throw new Error("LP value is unavailable");
}
const baseToken = isToken0Based ? token0 : token1;
const baseDecimals = isToken0Based ? token0Decimals : token1Decimals;
const userLpValue = (userShares * totalLpValue) / totalSupply;
Use computeLpValue(maximise) only when you need the same directional valuation used by pool operations. Deposits use maximised pricing; withdrawals use conservative pricing. For executable deposit or withdrawal estimates, prefer the Lens preview methods shown above.
Next Steps
- Contracts and ABIs - Contract registry and ABI usage