import {
ActionType,
OrderSide,
OrderType,
PerpsClient,
getMarkets,
getProviders,
getTermsAcceptance,
} from '@lifi/perps-sdk';
import { hyperliquidProvider } from '@lifi/perps-sdk-provider-hyperliquid';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrum } from 'viem/chains';
// --- Wallet setup (viem with private key) ---
const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const walletClient = createWalletClient({
account,
chain: arbitrum,
transport: http(),
});
const userAddress = account.address;
// --- Wallet setup (wagmi — browser) ---
// import { useWalletClient, useAccount } from 'wagmi';
//
// const { data: walletClient } = useWalletClient();
// const { address: userAddress } = useAccount();
// 1. Create the client and attach the user wallet (used for setup and user-signed actions such as withdrawals and transfers).
// Register the provider plugin so per-user reads and actions (checkSetup,
// placeOrder, ...) can resolve the venue; without it they throw.
const perps = new PerpsClient({
integrator: 'my-app',
apiKey: 'your-api-key',
providers: [hyperliquidProvider()],
});
perps.setUserWallet(walletClient);
// 2. Fetch available providers and markets
const { providers } = await getProviders(perps.client);
console.log(providers.map((p) => p.name)); // ['Hyperliquid', ...]
const { markets } = await getMarkets(perps.client, { provider: 'hyperliquid' });
console.log(markets.map((m) => m.baseAsset.displaySymbol)); // ['BTC', 'ETH', 'SOL', ...]
// 3. Accept the current terms. Every provider action, including checkSetup on an
// existing account, fails with 401 / 2012 (TermsNotAccepted) until the address
// has accepted the current terms version. Repeat after a terms-version bump.
const terms = await getTermsAcceptance(perps.client, userAddress);
if (!terms.accepted) {
await perps.submitOnboarding({
address: userAddress,
termsVersion: terms.termsVersion,
});
}
// 4. One-time setup: check, then run each outstanding step. The user's wallet
// signs APPROVE_AGENT (and any other still-pending setup actions); after this
// completes the SDK-managed agent is on-chain and trading proceeds without
// further wallet prompts. `required.setup` holds only USER-signed steps that
// are not choices: the SDK stages them for you to show. SDK-signed steps
// (such as the referral) are applied inside checkSetup without a wallet
// prompt. A choice step (Hyperliquid
// accountMode) is listed on `required.checklist` with its `selected` option
// and is never staged; the SDK does not apply it. An unsatisfied choice keeps
// `isReady` false. Apply one with `executeProviderOption`.
const provider = 'hyperliquid';
let required = await perps.checkSetup({ provider, address: userAddress });
if (required.accountExists === false) {
// An address with no venue account has no setup steps yet: fund it first.
const flow = await perps.getDepositFlow({ provider, address: userAddress });
throw new Error(`Deposit funds first (deposit flow: ${flow?.kind})`);
}
while (!required.isReady) {
// The checklist is in sequence order. Handle its first unsatisfied item.
const item = required.checklist.find((entry) => !entry.satisfied);
if (!item) break;
const { options } = item.descriptor;
if (options !== null) {
// Show the options and let the user choose. This example takes the recommended one.
const option = options.find((o) => o.default === true) ?? options[0];
await perps.executeProviderOption({
provider,
address: userAddress,
option,
});
} else {
const step = required.setup.find((s) => s.action === item.descriptor.type);
if (!step) break;
// executeProviderSetupAction picks the right signing path per step (EIP-712,
// EVM tx, or Lighter's hybrid WASM+EIP-191) and submits it, using the user
// wallet above.
await perps.executeProviderSetupAction({
provider,
address: userAddress,
step,
checklist: required.checklist,
});
}
// The checklist is a snapshot. Read it again after each step.
required = await perps.checkSetup({ provider, address: userAddress });
}
if (!required.isReady) {
throw new Error('Setup is not complete yet; run checkSetup again');
}
// 5. Place an order — agent signs automatically, no wallet popup.
const result = await perps.placeOrder({
address: userAddress,
provider: 'hyperliquid',
market: { marketId: 'BTC', categoryId: 'hyperliquid' },
side: OrderSide.BUY,
type: OrderType.MARKET,
size: '0.1',
price: '95500.00',
leverage: 10,
});
// A venue rejection does not throw: check every result.
console.log(result.results);
// [
// { action: 'updateLeverage', success: true },
// { action: 'placeOrder', success: true, orderId: '12345678' },
// ]
const placed = result.results.find((r) => r.action === ActionType.PLACE_ORDER);
if (!placed?.success) {
console.error(placed?.errorCode, placed?.error);
}