@usdh-kit/sdk
v0.2.0
Published
TypeScript SDK for USDH on Hyperliquid
Downloads
72
Maintainers
Readme
@usdh-kit/sdk
Current SDK version: 0.1.0
TypeScript SDK for USDH on Hyperliquid.
USDH is the native stablecoin on Hyperliquid, issued by Bridge and designed by Native Markets, with 50% of reserve revenue routed to the Hyperliquid Assistance Fund. @usdh-kit/sdk ships the retail-side plumbing (pair resolution, signing, transport) so apps and bots can convert into USDH without writing the Hyperliquid action layer themselves.
Contributors: Yaugourt · Sumfxn
Status
Pre-release. Public API is unstable until 1.0.0.
What works today:
getQuote()andswap()forUSDC → USDHend to end (signing + msgpack + IOC limit submission)bridgeToCore()for moving USDC from HyperEVM to HyperCore, with credit pollinggetHypercoreBalance()for spendable HyperCore balances (total - hold)getRoute()/preflightSwap()for HyperCore-vs-HyperEVM source selectionbridgeAndSwap()for route → optional bridge → swap orchestration- Read-only
InfoClient(spotMeta, spotClearinghouseState, L2 book)
Deferred to follow-up PRs: USDT pricing/swap, reverse direction (USDH → USDC), multi-chain source.
Install
pnpm add @usdh-kit/sdkQuickstart
import { BridgeTimeoutError, createUsdhKit, isBridgeAndSwapError } from '@usdh-kit/sdk'
const kit = createUsdhKit({ network: 'mainnet', signer, evmWallet, slippageBps: 30 })
const amount = 11_000_000n // 11 USDC; Hyperliquid spot orders must be >10 USDC
try {
const result = await kit.bridgeAndSwap({
from: 'USDC',
amount,
onProgress: (event) => console.log(event.phase),
})
console.log(`got ${result.swap.received} USDH for ${result.swap.spent} USDC`)
} catch (err) {
if (isBridgeAndSwapError(err)) {
console.error(`${err.phase} failed`, err.cause)
if (err.cause instanceof BridgeTimeoutError) {
console.log(`bridge tx ${err.cause.txHash} is still pending HyperCore credit`)
}
}
throw err
}swap() submits an IOC limit order priced slippageBps above the mid (max
slippage is enforced pre-fill by Hyperliquid's matcher). The returned
result.slippageBps is the realised slippage versus mid; tighten the tolerance
and retry if it's higher than you expected.
Agent wallets
Browser wallets can reject Hyperliquid L1 order signatures because orders use Hyperliquid's Exchange typed-data domain (chainId: 1337), not the connected HyperEVM chain. The recommended production pattern is a Hyperliquid API/agent wallet:
- the master wallet approves the agent once with
approveAgent() - the agent signs L1 trading actions
- reads, routing, balances, and bridge ownership still use the master account via
accountAddress
import { approveAgent, createUsdhKit } from '@usdh-kit/sdk'
await approveAgent({
network: 'mainnet',
signer: masterWalletSigner,
agentAddress: agentSigner.address,
agentName: 'my-app-usdh',
signatureChainId: 999, // match the connected HyperEVM chain for browser wallets
})
const kit = createUsdhKit({
network: 'mainnet',
signer: agentSigner,
accountAddress: masterWalletSigner.address,
evmWallet: masterEvmWallet,
slippageBps: 30,
})Backend and bot builders can provide an already-approved agent signer from a private key or key-management system. Frontend builders should keep any generated browser agent short-lived, scoped to the current account/network, and avoid logging private keys, raw signatures, or full typed-data payloads.
Quote a swap
getQuote() returns a mid-price snapshot with a 30-second validity window.
const quote = await kit.getQuote({ from: 'USDC', amount: 11_000_000n })
if (Date.now() < quote.validUntil) {
console.log(`mid-price on ${quote.pair}: ${quote.midPrice}`)
console.log(`would receive ~${quote.estimatedReceived} USDH`)
}Route and preflight
getHypercoreBalance() returns total, held, and spendable HyperCore balance for a source stable. getRoute() decides whether the user can swap directly from HyperCore or needs to bridge from HyperEVM first. It checks spendable HyperCore source balance (total - hold), applies the configured slippage plus a small HC fee buffer, and returns a quote alongside the route decision.
const balance = await kit.getHypercoreBalance({ asset: 'USDC' })
console.log(`spendable HC USDC: ${balance.available}`)
const route = await kit.preflightSwap({ from: 'USDC', amount: 11_000_000n })
if (!route.canSwap) {
console.log(route.blockReason)
}
if (route.requiresBridge) {
console.log('will bridge from HyperEVM before swapping')
}getRoute() does not inspect the user's HyperEVM ERC20 balance. If it selects
the bridge route, canSwap only means the kit has an evmWallet configured;
the wallet/RPC will still reject an underfunded bridge transaction.
Bridge and swap
bridgeAndSwap() composes the common retail flow:
- route/preflight
- bridge from HyperEVM when required
- swap on HyperCore
It returns both legs when a bridge happened and emits progress events that can directly drive UI state:
const result = await kit.bridgeAndSwap({
from: 'USDC',
amount: 11_000_000n,
onProgress: (event) => {
if (event.phase === 'bridging') showBridgeSpinner()
if (event.phase === 'swapping') showSwapSpinner()
},
})
console.log(result.route.sourceChain)
console.log(result.bridge?.txHash)
console.log(result.swap.orderId)Unexpected route, bridge, or swap failures are wrapped in BridgeAndSwapError. Use isBridgeAndSwapError(err) to narrow the type, then inspect phase, route, optional bridge, and cause so apps can show accurate recovery copy without parsing strings. If cause is BridgeTimeoutError, the EVM transfer was sent and you can show cause.txHash while waiting/retrying for HyperCore credit. Preflight blockers still throw their specific errors (MissingEvmWalletError, InsufficientBalanceError).
Features (V1)
USDC → USDHquote and swap via the canonical HL spot pair- HyperEVM → HyperCore bridge with credit polling (
bridgeToCore) getRoute()/preflightSwap()route selection and preflight metadatabridgeAndSwap()high-level orchestration with progress callbacks- Wallet-agnostic
Signerinterface (works with viem, ethers, Privy, Turnkey, raw private key) - Read-only
InfoClient(spotMeta, spot clearinghouse state, L2 book) - Typed error hierarchy rooted at
UsdhKitError, includingBridgeAndSwapErrorphase/cause context andisBridgeAndSwapError()narrowing - npm provenance on every release
- Mainnet and testnet support, no signing on read paths
Runtime support
- Node.js >= 18.18 (native
fetch,AbortController,bigint) - Bun >= 1.1
- Modern evergreen browsers (Chrome >= 107, Safari >= 16, Firefox >= 104)
- Edge runtimes (Cloudflare Workers, Vercel Edge)
