Skip to content

Hyperliquid integration

This recipe shows a full Hyperliquid integration: depositing USDC via Bridge2 on Arbitrum and signing HL Core L1 actions (orders, transfers, withdrawals) with the same Sigil wallet.

TL;DR

  • The address that HL sees is useWallet().address — your Sigil EOA on the EVM networks. Pass it to HL’s APIs, the builder code allowlist, anywhere HL asks for a user.
  • Use useSendTransaction to call HL Bridge2’s deposit. The user pays gas in ETH on Arbitrum.
  • Use useSignTypedData for HL Core L1 actions (orders, transfers, withdraws). HL Core verifies them with raw ecrecover over an EIP-712 digest.

1. Deposit USDC from Arbitrum to HL Bridge2

The user already has USDC on their Sigil wallet on Arbitrum (your app’s funding flow puts it there). HL’s Bridge2 takes a normal Arbitrum tx: two calls — USDC.approve(bridge, amount) and Bridge2.deposit(amount).

import { useSendTransaction, useWallet } from '@sigilkeys/sdk/react';
import { encodeFunctionData, parseUnits } from 'viem';
const BRIDGE2 = '0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7';
const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
export function DepositButton({ amount }: { amount: string }) {
const { address } = useWallet();
const { sendTransaction, isLoading } = useSendTransaction();
async function deposit() {
const amountWei = parseUnits(amount, 6); // USDC has 6 decimals
// 1) Approve the bridge to pull USDC from the wallet.
await sendTransaction({
chain: 'arbitrum',
to: USDC_ARB,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [BRIDGE2, amountWei],
}),
preview: { title: 'Approve USDC for Hyperliquid' },
});
// 2) Call Bridge2.deposit. msg.sender will be the wallet EOA.
const r = await sendTransaction({
chain: 'arbitrum',
to: BRIDGE2,
data: encodeFunctionData({
abi: hlBridge2Abi,
functionName: 'deposit',
args: [amountWei],
}),
preview: {
title: 'Deposit to Hyperliquid',
description: `${amount} USDC -> HL Core`,
fields: [{ label: 'HL user', value: address! }],
},
});
console.log('arbitrum tx:', r.txHash);
// HL Core credits the wallet address with `amount` USDC within ~30 s.
}
return <button disabled={isLoading} onClick={deposit}>Deposit</button>;
}

Gas: the user’s wallet pays in ETH on Arbitrum. Make sure your funding flow drops in enough ETH before this screen — a typical Bridge2 deposit costs ~0.0001 ETH at current Arbitrum gas, but plan for 10x that to absorb spikes.

2. Sign Hyperliquid L1 actions

HL Core actions (place order, cancel, transfer USDC inside HL, withdraw back to Arbitrum) are EIP-712 typed-data messages submitted to HL’s sequencer over WebSocket / HTTP. Sigil signs them with the wallet’s EOA key.

import { useSignTypedData, useWallet } from '@sigilkeys/sdk/react';
// HL's typed-data domain + types for the "place order" action.
// (Lifted verbatim from HL SDK so the signature recovers to the same EOA
// the sequencer expects; copy/paste from your HL client of choice.)
const HL_DOMAIN = {
name: 'HyperliquidSignTransaction',
version: '1',
chainId: 421614, // HL uses Arbitrum Sepolia ids; check current HL docs
verifyingContract: '0x0000000000000000000000000000000000000000',
};
export function PlaceOrderButton({ order }: { order: HLOrder }) {
const { address } = useWallet();
const { signTypedData, isLoading } = useSignTypedData();
async function place() {
const { signature } = await signTypedData({
domain: HL_DOMAIN,
types: HL_ORDER_TYPES,
primaryType: 'Order',
message: { ...order, user: address },
});
await hlClient.submit({ action: order, signature, user: address });
}
return <button disabled={isLoading} onClick={place}>Place order</button>;
}

The signature comes from the wallet’s EOA key, exactly what HL Core ecrecovers.

3. Builder codes

HL’s builder code system rewards apps that route order flow through them. The builder code is bound to the wallet address. Pass address when HL asks for the user.

If you want users to opt into your builder code at first deposit, sign HL’s “approve builder fee” action with useSignTypedData and submit it right after the deposit confirms.

What about gasless deposits?

Bridge2 has batchedDepositWithPermit(...). The flow is: user signs an EIP-2612 permit on USDC + a deposit auth via useSignTypedData, your backend (or any relayer) calls batchedDepositWithPermit with those signatures and pays the Arbitrum gas. The user ends up funded on HL without ever holding ETH.

That’s a custom relayer you build and operate. The SDK call shape stays the same — you’d use useSignTypedData for both signatures and never call useSendTransaction at all on this path.

Common errors

  • HL says “deposit not found” after Arbitrum tx confirmed. HL Core takes ~30 s to index Bridge2 events. If it never appears, double-check the tx’s from address matches the wallet address you’re using in HL.
  • “insufficient funds for gas” on the deposit tx. The wallet doesn’t have enough ETH on Arbitrum. Bridge ETH first (or onramp directly to the wallet).
  • estimate_gas_reverted from sendTransaction. Most often missing approval (run approve first) or wrong calldata. Pass gasLimit explicitly if you’ve confirmed the call is correct.
  • HL signature rejects with “invalid signature”. Wrong domain (HL changes chainId across testnet/mainnet) or wrong types. Compare your EIP-712 payload against the HL SDK’s reference for the same action.