Skip to content

Commit bcc1017

Browse files
authored
Merge branch 'main' into fix/recipient-account-icons
2 parents 71ada07 + b7bda99 commit bcc1017

File tree

57 files changed

+867
-346
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+867
-346
lines changed

app/actions/settings/index.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,6 @@ export function setShowHexData(showHexData) {
1212
};
1313
}
1414

15-
export function setShowCustomNonce(showCustomNonce) {
16-
return {
17-
type: 'SET_SHOW_CUSTOM_NONCE',
18-
showCustomNonce,
19-
};
20-
}
21-
2215
export function setShowFiatOnTestnets(showFiatOnTestnets) {
2316
return {
2417
type: 'SET_SHOW_FIAT_ON_TESTNETS',

app/components/UI/Perps/constants/hyperLiquidConfig.ts

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,33 +114,47 @@ export const FEE_RATES: FeeRatesConfig = {
114114
};
115115

116116
/**
117-
* HIP-3 fee multiplier configuration
117+
* HIP-3 dynamic fee calculation configuration
118118
*
119-
* HIP-3 (builder-deployed) perpetual markets charge 2x base fees compared to
120-
* validator-operated markets. This covers a 50/50 split between the protocol
121-
* and the builder/deployer.
119+
* HIP-3 (builder-deployed) perpetual markets have variable fees based on:
120+
* 1. deployerFeeScale - Per-DEX fee multiplier (fetched from perpDexs API)
121+
* 2. growthMode - Per-asset 90% fee reduction (fetched from meta API)
122122
*
123-
* Reference: HIP-3.md line 45 - "From the user perspective, fees are 2x the
124-
* usual fees on validator-operated perp markets."
123+
* Fee Formula (from HyperLiquid docs):
124+
* - scaleIfHip3 = deployerFeeScale < 1 ? deployerFeeScale + 1 : deployerFeeScale * 2
125+
* - growthModeScale = growthMode ? 0.1 : 1
126+
* - finalRate = baseRate * scaleIfHip3 * growthModeScale
125127
*
126-
* Applied to:
127-
* - Base fee rates (taker/maker)
128-
* - User-specific discounted rates
129-
* - All fee calculations for HIP-3 assets (identified by dex:SYMBOL format)
128+
* Example: For xyz:TSLA with deployerFeeScale=1.0 and growthMode="enabled":
129+
* - scaleIfHip3 = 1.0 * 2 = 2.0
130+
* - growthModeScale = 0.1 (90% reduction)
131+
* - Final multiplier = 2.0 * 0.1 = 0.2 (effectively 80% off standard 2x HIP-3 fees)
130132
*
131-
* Example: For xyz:TSLA (HIP-3 asset):
132-
* - Base taker rate: 0.045%
133-
* - After HIP-3 multiplier: 0.045% × 2 = 0.090%
134-
* - Protocol receives: 0.045% (same as regular markets)
135-
* - Builder receives: 0.045% (deployed market incentive)
136-
*
137-
* @see HIP-3.md for detailed protocol specification
133+
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees#fee-formula-for-developers
138134
* @see parseAssetName() in HyperLiquidProvider for HIP-3 asset detection
139135
*/
140136
export const HIP3_FEE_CONFIG = {
141137
/**
142-
* Fee multiplier for HIP-3 assets (2x base rate)
143-
* Covers 50% deployer share + 50% protocol share
138+
* Growth Mode multiplier - 90% fee reduction for assets in growth phase
139+
* This is a protocol constant from HyperLiquid's fee formula
140+
*/
141+
GROWTH_MODE_SCALE: 0.1,
142+
143+
/**
144+
* Default deployerFeeScale when API is unavailable
145+
* Most HIP-3 DEXs use 1.0, which results in 2x base fees
146+
*/
147+
DEFAULT_DEPLOYER_FEE_SCALE: 1.0,
148+
149+
/**
150+
* Cache TTL for perpDexs data (5 minutes)
151+
* Fee scales rarely change, so longer cache is acceptable
152+
*/
153+
PERP_DEXS_CACHE_TTL_MS: 5 * 60 * 1000,
154+
155+
/**
156+
* @deprecated Use dynamic calculation via calculateHip3FeeMultiplier()
157+
* Kept for backwards compatibility during migration
144158
*/
145159
FEE_MULTIPLIER: 2,
146160
} as const;

app/components/UI/Perps/controllers/providers/HyperLiquidProvider.ts

Lines changed: 144 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ import type {
125125
UserHistoryItem,
126126
} from '../types';
127127
import { PERPS_ERROR_CODES } from '../PerpsController';
128+
import type {
129+
ExtendedAssetMeta,
130+
ExtendedPerpDex,
131+
} from '../../types/perps-types';
128132

129133
// Helper method parameter interfaces (module-level for class-dependent methods only)
130134
interface GetAssetInfoParams {
@@ -224,6 +228,13 @@ export class HyperLiquidProvider implements IPerpsProvider {
224228
// Filtering is applied on-demand (cheap array operations) - no need for separate processed cache
225229
private cachedMetaByDex = new Map<string, MetaResponse>();
226230

231+
// Cache for perpDexs data (deployerFeeScale for dynamic fee calculation)
232+
// TTL-based cache - fee scales rarely change
233+
private perpDexsCache: {
234+
data: ExtendedPerpDex[] | null;
235+
timestamp: number;
236+
} = { data: null, timestamp: 0 };
237+
227238
// Session cache for referral state (cleared on disconnect/reconnect)
228239
// Key: `network:userAddress`, Value: true if referral is set
229240
private referralCheckCache = new Map<string, boolean>();
@@ -644,6 +655,116 @@ export class HyperLiquidProvider implements IPerpsProvider {
644655
return meta;
645656
}
646657

658+
/**
659+
* Fetch perpDexs data with TTL-based caching
660+
* Returns deployerFeeScale info needed for dynamic fee calculation
661+
* @returns Array of ExtendedPerpDex objects (null entries represent main DEX)
662+
*/
663+
private async getCachedPerpDexs(): Promise<ExtendedPerpDex[]> {
664+
const now = Date.now();
665+
666+
// Return cached data if still valid
667+
if (
668+
this.perpDexsCache.data &&
669+
now - this.perpDexsCache.timestamp <
670+
HIP3_FEE_CONFIG.PERP_DEXS_CACHE_TTL_MS
671+
) {
672+
DevLogger.log('[getCachedPerpDexs] Using cached perpDexs data', {
673+
age: `${Math.round((now - this.perpDexsCache.timestamp) / 1000)}s`,
674+
count: this.perpDexsCache.data.length,
675+
});
676+
return this.perpDexsCache.data;
677+
}
678+
679+
// Fetch fresh data from API
680+
// Note: SDK types are incomplete, but API returns deployerFeeScale
681+
this.ensureClientsInitialized();
682+
const infoClient = this.clientService.getInfoClient();
683+
const perpDexs =
684+
(await infoClient.perpDexs()) as unknown as ExtendedPerpDex[];
685+
686+
// Cache the result
687+
this.perpDexsCache = { data: perpDexs, timestamp: now };
688+
689+
DevLogger.log('[getCachedPerpDexs] Fetched and cached perpDexs data', {
690+
count: perpDexs.length,
691+
dexes: perpDexs
692+
.filter((d) => d !== null)
693+
.map((d) => ({
694+
name: d.name,
695+
deployerFeeScale: d.deployerFeeScale,
696+
})),
697+
});
698+
699+
return perpDexs;
700+
}
701+
702+
/**
703+
* Calculate HIP-3 fee multiplier using HyperLiquid's official formula
704+
* Fetches deployerFeeScale from perpDexs API and growthMode from meta API
705+
*
706+
* Formula from HyperLiquid docs:
707+
* - scaleIfHip3 = deployerFeeScale < 1 ? deployerFeeScale + 1 : deployerFeeScale * 2
708+
* - growthModeScale = growthMode ? 0.1 : 1
709+
* - finalMultiplier = scaleIfHip3 * growthModeScale
710+
*
711+
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees#fee-formula-for-developers
712+
*/
713+
private async calculateHip3FeeMultiplier(params: {
714+
dexName: string;
715+
assetSymbol: string;
716+
}): Promise<number> {
717+
const { dexName, assetSymbol } = params;
718+
719+
try {
720+
// Get deployerFeeScale from perpDexs
721+
const perpDexs = await this.getCachedPerpDexs();
722+
const dexInfo = perpDexs.find((d) => d?.name === dexName);
723+
const parsedScale = parseFloat(dexInfo?.deployerFeeScale ?? '');
724+
const deployerFeeScale = Number.isNaN(parsedScale)
725+
? HIP3_FEE_CONFIG.DEFAULT_DEPLOYER_FEE_SCALE
726+
: parsedScale;
727+
728+
// Get growthMode from meta for this specific asset
729+
const meta = await this.getCachedMeta({ dexName });
730+
const fullAssetName = `${dexName}:${assetSymbol}`;
731+
const assetMeta = meta.universe.find(
732+
(u) => (u as ExtendedAssetMeta).name === fullAssetName,
733+
) as ExtendedAssetMeta | undefined;
734+
const isGrowthMode = assetMeta?.growthMode === 'enabled';
735+
736+
// Apply official formula
737+
const scaleIfHip3 =
738+
deployerFeeScale < 1 ? deployerFeeScale + 1 : deployerFeeScale * 2;
739+
const growthModeScale = isGrowthMode
740+
? HIP3_FEE_CONFIG.GROWTH_MODE_SCALE
741+
: 1;
742+
743+
const finalMultiplier = scaleIfHip3 * growthModeScale;
744+
745+
DevLogger.log('HIP-3 Dynamic Fee Calculation', {
746+
dexName,
747+
assetSymbol,
748+
fullAssetName,
749+
deployerFeeScale,
750+
isGrowthMode,
751+
scaleIfHip3,
752+
growthModeScale,
753+
finalMultiplier,
754+
});
755+
756+
return finalMultiplier;
757+
} catch (error) {
758+
DevLogger.log('HIP-3 Fee Calculation Failed, using fallback', {
759+
dexName,
760+
assetSymbol,
761+
error: ensureError(error).message,
762+
});
763+
// Safe fallback: standard HIP-3 2x multiplier (no Growth Mode discount)
764+
return HIP3_FEE_CONFIG.DEFAULT_DEPLOYER_FEE_SCALE * 2;
765+
}
766+
}
767+
647768
/**
648769
* Generate session cache key for user-specific caches
649770
* Format: "network:userAddress" (address normalized to lowercase)
@@ -5071,21 +5192,27 @@ export class HyperLiquidProvider implements IPerpsProvider {
50715192
let feeRate =
50725193
orderType === 'market' || !isMaker ? FEE_RATES.taker : FEE_RATES.maker;
50735194

5074-
// HIP-3 assets have 2× base fees (per fees.md line 9)
5075-
// Parse coin to detect HIP-3 DEX (e.g., "xyz:TSLA" → dex="xyz")
5076-
const { dex } = parseAssetName(coin);
5195+
// Parse coin to detect HIP-3 DEX (e.g., "xyz:TSLA" → dex="xyz", symbol="TSLA")
5196+
const { dex, symbol } = parseAssetName(coin);
50775197
const isHip3Asset = dex !== null;
50785198

5079-
if (isHip3Asset) {
5199+
// Calculate HIP-3 fee multiplier dynamically (handles Growth Mode)
5200+
let hip3Multiplier = 1;
5201+
if (isHip3Asset && dex && symbol) {
5202+
hip3Multiplier = await this.calculateHip3FeeMultiplier({
5203+
dexName: dex,
5204+
assetSymbol: symbol,
5205+
});
50805206
const originalRate = feeRate;
5081-
feeRate *= HIP3_FEE_CONFIG.FEE_MULTIPLIER;
5207+
feeRate *= hip3Multiplier;
50825208

5083-
DevLogger.log('HIP-3 Fee Multiplier Applied', {
5209+
DevLogger.log('HIP-3 Dynamic Fee Multiplier Applied', {
50845210
coin,
50855211
dex,
5212+
symbol,
50865213
originalBaseRate: originalRate,
50875214
hip3BaseRate: feeRate,
5088-
multiplier: HIP3_FEE_CONFIG.FEE_MULTIPLIER,
5215+
hip3Multiplier,
50895216
});
50905217
}
50915218

@@ -5095,6 +5222,7 @@ export class HyperLiquidProvider implements IPerpsProvider {
50955222
amount,
50965223
coin,
50975224
isHip3Asset,
5225+
hip3Multiplier,
50985226
baseFeeRate: feeRate,
50995227
baseTakerRate: FEE_RATES.taker,
51005228
baseMakerRate: FEE_RATES.maker,
@@ -5119,9 +5247,9 @@ export class HyperLiquidProvider implements IPerpsProvider {
51195247
? cached.perpsTakerRate
51205248
: cached.perpsMakerRate;
51215249

5122-
// Apply HIP-3 multiplier to user-specific rates
5123-
if (isHip3Asset) {
5124-
userFeeRate *= HIP3_FEE_CONFIG.FEE_MULTIPLIER;
5250+
// Apply HIP-3 dynamic multiplier to user-specific rates (includes Growth Mode)
5251+
if (isHip3Asset && hip3Multiplier > 0) {
5252+
userFeeRate *= hip3Multiplier;
51255253
}
51265254

51275255
feeRate = userFeeRate;
@@ -5134,6 +5262,7 @@ export class HyperLiquidProvider implements IPerpsProvider {
51345262
spotMakerRate: cached.spotMakerRate,
51355263
selectedRate: feeRate,
51365264
isHip3Asset,
5265+
hip3Multiplier,
51375266
cacheExpiry: new Date(cached.timestamp + cached.ttl).toISOString(),
51385267
cacheAge: `${Math.round((Date.now() - cached.timestamp) / 1000)}s`,
51395268
});
@@ -5245,9 +5374,9 @@ export class HyperLiquidProvider implements IPerpsProvider {
52455374
? rates.perpsTakerRate
52465375
: rates.perpsMakerRate;
52475376

5248-
// Apply HIP-3 multiplier to API-fetched rates
5249-
if (isHip3Asset) {
5250-
userFeeRate *= HIP3_FEE_CONFIG.FEE_MULTIPLIER;
5377+
// Apply HIP-3 dynamic multiplier to API-fetched rates (includes Growth Mode)
5378+
if (isHip3Asset && hip3Multiplier > 0) {
5379+
userFeeRate *= hip3Multiplier;
52515380
}
52525381

52535382
feeRate = userFeeRate;
@@ -5257,6 +5386,7 @@ export class HyperLiquidProvider implements IPerpsProvider {
52575386
selectedRatePercentage: `${(feeRate * 100).toFixed(4)}%`,
52585387
discountApplied: perpsTakerRate < FEE_RATES.taker,
52595388
isHip3Asset,
5389+
hip3Multiplier,
52605390
cacheExpiry: new Date(rates.timestamp + rates.ttl).toISOString(),
52615391
});
52625392
}
@@ -5389,6 +5519,7 @@ export class HyperLiquidProvider implements IPerpsProvider {
53895519
this.referralCheckCache.clear();
53905520
this.builderFeeCheckCache.clear();
53915521
this.cachedMetaByDex.clear();
5522+
this.perpDexsCache = { data: null, timestamp: 0 };
53925523

53935524
// Clear pending promise trackers to prevent memory leaks and ensure clean state
53945525
this.ensureReadyPromise = null;

app/components/UI/Perps/types/perps-types.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,33 @@ export interface ReconnectOptions {
8787
*/
8888
force?: boolean;
8989
}
90+
91+
/**
92+
* Extended asset metadata including Growth Mode fields not in SDK types.
93+
* The HyperLiquid API returns these fields but the SDK doesn't type them.
94+
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees#fee-formula-for-developers
95+
*/
96+
export interface ExtendedAssetMeta {
97+
name: string;
98+
szDecimals: number;
99+
maxLeverage: number;
100+
/** Per-asset Growth Mode status - "enabled" means 90% fee reduction */
101+
growthMode?: 'enabled' | null;
102+
/** ISO timestamp of last Growth Mode change */
103+
lastGrowthModeChangeTime?: string;
104+
}
105+
106+
/**
107+
* Extended perp DEX info including fee scale fields not in SDK types.
108+
* The HyperLiquid API returns these fields but the SDK doesn't type them.
109+
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees#fee-formula-for-developers
110+
*/
111+
export interface ExtendedPerpDex {
112+
name: string;
113+
fullName?: string;
114+
deployer?: string;
115+
/** DEX-level fee scale (e.g., "1.0" for xyz DEX) - determines HIP-3 multiplier */
116+
deployerFeeScale?: string;
117+
/** ISO timestamp of last fee scale change */
118+
lastDeployerFeeScaleChangeTime?: string;
119+
}

0 commit comments

Comments
 (0)