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
useSendTransactionto call HL Bridge2’sdeposit. The user pays gas in ETH on Arbitrum. - Use
useSignTypedDatafor HL Core L1 actions (orders, transfers, withdraws). HL Core verifies them with rawecrecoverover 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
fromaddress 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_revertedfromsendTransaction. Most often missing approval (runapprovefirst) or wrong calldata. PassgasLimitexplicitly 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.