Skip to content

Commit e27e88a

Browse files
authored
Support hyperliquid balance fetching for spot coins (#884)
* Support hyperliquid balance fetching for spot coins * feat: changeset * fix export name
1 parent 8854ced commit e27e88a

File tree

5 files changed

+146
-92
lines changed

5 files changed

+146
-92
lines changed

.changeset/ten-files-tell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@relayprotocol/relay-kit-ui': patch
3+
---
4+
5+
Support hyperliquid balance fetching for spot coins

packages/ui/src/hooks/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import useFallbackState from './useFallbackState.js'
2121
import useMoonPayTransaction from './useMoonPayTransaction.js'
2222
import { useInternalRelayChains } from './useInternalRelayChains.js'
2323
import useGasTopUpRequired from './useGasTopUpRequired.js'
24-
import useHyperliquidUsdcBalance from './useHyperliquidUsdcBalance.js'
24+
import useHyperliquidBalance from './useHyperliquidBalance.js'
2525
import useEOADetection from './useEOADetection.js'
2626
import useTransactionCount from './useTransactionCount.js'
2727
import useTronBalance from './useTronBalance.js'
@@ -50,7 +50,7 @@ export {
5050
useMoonPayTransaction,
5151
useInternalRelayChains,
5252
useGasTopUpRequired,
53-
useHyperliquidUsdcBalance,
53+
useHyperliquidBalance,
5454
useEOADetection,
5555
useTransactionCount,
5656
useTronBalance

packages/ui/src/hooks/useCurrencyBalance.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { isValidAddress } from '../utils/address.js'
1717
import useRelayClient from './useRelayClient.js'
1818
import useEclipseBalance from '../hooks/useEclipseBalance.js'
1919
import { eclipse } from '../utils/solana.js'
20-
import useHyperliquidUsdcBalance from './useHyperliquidUsdcBalance.js'
20+
import useHyperliquidBalance from './useHyperliquidBalance.js'
2121
import useTronBalance from '../hooks/useTronBalance.js'
2222

2323
type UseBalanceProps = {
@@ -168,18 +168,22 @@ const useCurrencyBalance = ({
168168
)
169169
})
170170

171-
const hyperliquidUsdcBalance = useHyperliquidUsdcBalance(address, {
172-
enabled: Boolean(
173-
!adaptedWalletBalanceIsEnabled &&
174-
chain &&
175-
chain.vmType === 'hypevm' &&
176-
address &&
177-
_isValidAddress &&
178-
enabled
179-
),
180-
gcTime: refreshInterval,
181-
staleTime: refreshInterval
182-
})
171+
const hyperliquidBalance = useHyperliquidBalance(
172+
address,
173+
currency as string,
174+
{
175+
enabled: Boolean(
176+
!adaptedWalletBalanceIsEnabled &&
177+
chain &&
178+
chain.vmType === 'hypevm' &&
179+
address &&
180+
_isValidAddress &&
181+
enabled
182+
),
183+
gcTime: refreshInterval,
184+
staleTime: refreshInterval
185+
}
186+
)
183187

184188
const tronBalance = useTronBalance(address, currency, {
185189
enabled: Boolean(
@@ -297,11 +301,11 @@ const useCurrencyBalance = ({
297301
}
298302
} else if (chain?.vmType === 'hypevm') {
299303
return {
300-
value: hyperliquidUsdcBalance.balance,
301-
queryKey: hyperliquidUsdcBalance.queryKey,
302-
isLoading: hyperliquidUsdcBalance.isLoading,
303-
isError: hyperliquidUsdcBalance.isError,
304-
error: hyperliquidUsdcBalance.error,
304+
value: hyperliquidBalance.balance,
305+
queryKey: hyperliquidBalance.queryKey,
306+
isLoading: hyperliquidBalance.isLoading,
307+
isError: hyperliquidBalance.isError,
308+
error: hyperliquidBalance.error,
305309
isDuneBalance: false
306310
}
307311
} else if (chain?.vmType === 'tvm') {
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { isAddress, parseUnits } from 'viem'
2+
import {
3+
useQuery,
4+
type DefaultError,
5+
type QueryKey
6+
} from '@tanstack/react-query'
7+
8+
export type HyperliquidMarginSummary = {
9+
accountValue?: string
10+
totalNtlPos?: string
11+
totalRawUsd?: string
12+
totalMarginUsed?: string
13+
}
14+
15+
export type HyperLiquidPerpsResponse = {
16+
marginSummary?: HyperliquidMarginSummary
17+
crossMarginSummary?: HyperliquidMarginSummary
18+
crossMaintenanceMarginUsed?: string
19+
withdrawable?: string
20+
assetPositions?: any[]
21+
time?: number
22+
}
23+
24+
export type HyperliquidSpotBalance = {
25+
coin: string
26+
token: number
27+
hold: string
28+
total: string
29+
entryNtl: string
30+
}
31+
32+
export type HyperliquidSpotResponse = {
33+
balances: HyperliquidSpotBalance[]
34+
}
35+
36+
type QueryType = typeof useQuery<
37+
string | undefined,
38+
DefaultError,
39+
string | undefined,
40+
QueryKey
41+
>
42+
type QueryOptions = Parameters<QueryType>['0']
43+
44+
// Perps USDC uses zero address
45+
const PERPS_USDC_ADDRESS = '0x00000000000000000000000000000000'
46+
47+
// Map currency addresses to Hyperliquid spot coin symbols and decimals
48+
const SPOT_TOKEN_CONFIG: Record<string, { coin: string; decimals: number }> = {
49+
'0x2e6d84f2d7ca82e6581e03523e4389f7': { coin: 'USDe', decimals: 2 },
50+
'0x54e00a5988577cb0b0c9ab0cb6ef7f4b': { coin: 'USDH', decimals: 2 }
51+
}
52+
53+
export default (
54+
address?: string,
55+
currency: string = PERPS_USDC_ADDRESS,
56+
queryOptions?: Partial<QueryOptions>
57+
) => {
58+
const isEvmAddress = isAddress(address ?? '')
59+
const isPerps = currency === PERPS_USDC_ADDRESS
60+
const spotConfig = SPOT_TOKEN_CONFIG[currency.toLowerCase()]
61+
const decimals = isPerps ? 8 : (spotConfig?.decimals ?? 2)
62+
63+
const queryKey = ['useHyperliquidBalance', address, currency]
64+
65+
const response = (useQuery as QueryType)({
66+
queryKey,
67+
queryFn: async () => {
68+
if (!address || !isEvmAddress) {
69+
return undefined
70+
}
71+
72+
if (isPerps) {
73+
// Fetch perps balance
74+
const res = await fetch('https://api.hyperliquid.xyz/info', {
75+
method: 'POST',
76+
headers: { 'Content-Type': 'application/json' },
77+
body: JSON.stringify({
78+
type: 'clearinghouseState',
79+
user: address
80+
})
81+
})
82+
const data = (await res.json()) as HyperLiquidPerpsResponse
83+
return data?.withdrawable
84+
} else if (spotConfig) {
85+
// Fetch spot balances
86+
const res = await fetch('https://api.hyperliquid.xyz/info', {
87+
method: 'POST',
88+
headers: { 'Content-Type': 'application/json' },
89+
body: JSON.stringify({
90+
type: 'spotClearinghouseState',
91+
user: address
92+
})
93+
})
94+
const data = (await res.json()) as HyperliquidSpotResponse
95+
// Find the balance matching the coin symbol
96+
const tokenBalance = data?.balances?.find(
97+
(b) => b.coin.toLowerCase() === spotConfig.coin.toLowerCase()
98+
)
99+
return tokenBalance?.total
100+
}
101+
return undefined
102+
},
103+
enabled: address !== undefined && isEvmAddress,
104+
...queryOptions
105+
})
106+
107+
const balance = parseUnits(response.data ?? '0', decimals)
108+
109+
return {
110+
...response,
111+
balance,
112+
queryKey
113+
} as ReturnType<QueryType> & {
114+
balance: bigint
115+
queryKey: (string | undefined)[]
116+
}
117+
}

packages/ui/src/hooks/useHyperliquidUsdcBalance.ts

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)