Skip to content

Commit 39a5fae

Browse files
committed
[core] Add testnet USDT
1 parent 667bf28 commit 39a5fae

File tree

3 files changed

+74
-33
lines changed

3 files changed

+74
-33
lines changed

packages/core/README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ USDT0 (EVM) → Bridge → USDT0 (Core) → Swap (spot 166) → USDC (Core)
7575
## Access Control & Authorization Matrix
7676

7777
### Authorization Flow
78+
7879
```
7980
CrestVault (Owner)
8081
├── authorizes → CrestTeller
@@ -88,17 +89,19 @@ CrestAccountant → readonly (provides exchange rates)
8889

8990
### Role Permissions
9091

91-
| Contract | Owner | Curator | Authorized | Public |
92-
|----------|--------|---------|------------|--------|
93-
| **CrestVault** | `authorize()`, `unauthorize()`, `setOwner()` | - | `manage()` | View functions |
94-
| **CrestTeller** | `setAccountant()`, `pause()`, `unpause()` | - | - | `deposit()`, `withdraw()` |
95-
| **CrestManager** | `setCurator()`, `pause()`, `unpause()` | `allocate()`, `rebalance()`, `closeAllPositions()` | - | View functions |
96-
| **CrestAccountant** | `updateExchangeRate()`, `setFees()`, `claimFees()` | - | - | `convertToShares()`, `convertToAssets()` |
92+
| Contract | Owner | Curator | Authorized | Public |
93+
| ------------------- | -------------------------------------------------- | -------------------------------------------------- | ---------- | ---------------------------------------- |
94+
| **CrestVault** | `authorize()`, `unauthorize()`, `setOwner()` | - | `manage()` | View functions |
95+
| **CrestTeller** | `setAccountant()`, `pause()`, `unpause()` | - | - | `deposit()`, `withdraw()` |
96+
| **CrestManager** | `setCurator()`, `pause()`, `unpause()` | `allocate()`, `rebalance()`, `closeAllPositions()` | - | View functions |
97+
| **CrestAccountant** | `updateExchangeRate()`, `setFees()`, `claimFees()` | - | - | `convertToShares()`, `convertToAssets()` |
9798

9899
## Core Functions
99100

100101
### CrestManager.allocate(uint32 spotIndex, uint32 perpIndex)
102+
101103
Opens new positions on Hyperliquid:
104+
102105
1. Validates no existing positions (`currentSpotPosition.size == 0`)
103106
2. Checks minimum balance (50 USDT0)
104107
3. Transfers USDT0 from vault via `vault.manage()`
@@ -109,7 +112,9 @@ Opens new positions on Hyperliquid:
109112
8. Updates vault's tracking indexes
110113

111114
### CrestManager.rebalance(uint32 newSpotIndex, uint32 newPerpIndex)
115+
112116
Rotates positions to new assets:
117+
113118
1. Validates existing positions
114119
2. Closes current positions via `_closeAllPositions()`:
115120
- Places IOC sell order for spot (0.5% below market)
@@ -119,28 +124,36 @@ Rotates positions to new assets:
119124
4. Does NOT automatically open new positions (requires separate `allocate()` call)
120125

121126
### CrestManager.closeAllPositions()
127+
122128
Exits all positions and returns funds:
129+
123130
1. Closes spot position (sell with IOC)
124131
2. Closes perp position (buy to close short with IOC)
125132
3. Swaps USDC → USDT0
126133
4. Bridges USDT0 back to EVM
127134
5. Emits `PositionClosed` events with realized PnL
128135

129136
### CrestTeller.deposit(uint256 assets, address receiver)
137+
130138
User deposits USDT0 for shares:
139+
131140
1. Calculates shares via `accountant.convertToShares()`
132141
2. Transfers USDT0 to vault
133142
3. Mints shares via `vault.enter()`
134143
4. Sets 1-day lock: `shareUnlockTime[receiver] = block.timestamp + shareLockPeriod`
135144

136145
### CrestTeller.withdraw(uint256 shares, address receiver)
146+
137147
User withdraws USDT0 by burning shares:
148+
138149
1. Checks lock period: `block.timestamp >= shareUnlockTime[msg.sender]`
139150
2. Calculates assets via `accountant.convertToAssets()`
140151
3. Burns shares and transfers USDT0 via `vault.exit()`
141152

142153
### CrestAccountant.updateExchangeRate()
154+
143155
Updates share/asset exchange rate:
156+
144157
1. Calculates total value (vault balance + positions)
145158
2. Applies platform fee (1% annually)
146159
3. Calculates performance fee if above high water mark (5% of profit)
@@ -159,7 +172,7 @@ Updates share/asset exchange rate:
159172

160173
```bash
161174
# Fork Hyperliquid mainnet
162-
forge test --fork-url https://rpc.hyperliquid.xyz/evm --compute-units-per-second 1
175+
forge test --fork-url https://rpc.hyperliquid.xyz/evm
163176

164177
# Use vm.deal() to allocate USDT0 in tests
165178
deal(USDT0_ADDRESS, user, amount);

packages/core/src/CrestManager.sol

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,35 @@ contract CrestManager is Auth, ReentrancyGuard {
2121
// ========================================= CONSTANTS =========================================
2222

2323
/**
24-
* @notice USDT0 token ID on Hyperliquid Core (bridgeable)
24+
* @notice USDC token ID on Hyperliquid Core (for trading)
2525
*/
26-
uint64 public constant USDT0_TOKEN_ID = 268;
26+
uint64 public constant USDC_TOKEN_ID = 0;
27+
28+
uint256 public constant TESTNET_CHAINID = 998;
2729

2830
/**
29-
* @notice USDT0 ERC20 address on Hyperliquid EVM
31+
* @notice USDT0 token ID on Hyperliquid Core (bridgeable)
3032
*/
31-
address public constant USDT0_ADDRESS = 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb;
33+
function usdt0TokenId() internal view returns (uint64) {
34+
return block.chainid == TESTNET_CHAINID ? 1204 : 268;
35+
}
3236

3337
/**
34-
* @notice USDC token ID on Hyperliquid Core (for trading)
38+
* @notice USDT0 ERC20 address on Hyperliquid EVM
3539
*/
36-
uint64 public constant USDC_TOKEN_ID = 0;
40+
function usdt0Address() internal view returns (address) {
41+
return
42+
block.chainid == TESTNET_CHAINID
43+
? 0x779Ded0c9e1022225f8E0630b35a9b54bE713736
44+
: 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb;
45+
}
3746

3847
/**
3948
* @notice USDT0/USDC spot index for swapping
4049
*/
41-
uint32 public constant USDT0_USDC_SPOT_INDEX = 166;
50+
function usdt0SpotIndex() internal view returns (uint32) {
51+
return block.chainid == TESTNET_CHAINID ? 1115 : 166;
52+
}
4253

4354
/**
4455
* @notice Allocation percentages (basis points)
@@ -191,21 +202,21 @@ contract CrestManager is Auth, ReentrancyGuard {
191202

192203
// Bridge USDT0 to Hyperliquid core
193204
CoreWriterLib.bridgeToCore(
194-
USDT0_ADDRESS,
205+
usdt0Address(),
195206
marginAmount + spotAmount + perpAmount
196207
);
197208

198209
// Swap USDT0 to USDC on Hyperliquid
199210
uint64 usdt0CoreAmount = HLConversions.evmToWei(
200-
USDT0_TOKEN_ID,
211+
usdt0TokenId(),
201212
marginAmount + spotAmount + perpAmount
202213
);
203214

204215
// Place market order to sell USDT0 for USDC
205216
CoreWriterLib.placeLimitOrder(
206-
USDT0_USDC_SPOT_INDEX,
217+
usdt0SpotIndex(),
207218
false, // sell USDT0
208-
PrecompileLib.spotPx(USDT0_USDC_SPOT_INDEX) - 10, // slight slippage for immediate fill
219+
PrecompileLib.spotPx(usdt0SpotIndex()) - 10, // slight slippage for immediate fill
209220
usdt0CoreAmount,
210221
false, // not reduce only
211222
3, // IOC
@@ -231,7 +242,7 @@ contract CrestManager is Auth, ReentrancyGuard {
231242
CoreWriterLib.placeLimitOrder(
232243
spotIndex,
233244
true, // isBuy
234-
spotPrice + (spotPrice * 50 / 10000), // 0.5% slippage
245+
spotPrice + ((spotPrice * 50) / 10000), // 0.5% slippage
235246
spotSizeInAsset,
236247
false, // reduceOnly
237248
3, // IOC (Immediate or Cancel)
@@ -257,7 +268,7 @@ contract CrestManager is Auth, ReentrancyGuard {
257268
CoreWriterLib.placeLimitOrder(
258269
perpIndex,
259270
false, // isBuy (short)
260-
perpPrice - (perpPrice * 50 / 10000), // 0.5% slippage
271+
perpPrice - ((perpPrice * 50) / 10000), // 0.5% slippage
261272
perpSizeInAsset,
262273
false, // reduceOnly
263274
3, // IOC (Immediate or Cancel)
@@ -345,7 +356,7 @@ contract CrestManager is Auth, ReentrancyGuard {
345356
CoreWriterLib.placeLimitOrder(
346357
currentSpotPosition.index,
347358
false, // isBuy (sell to close)
348-
currentSpotPrice - (currentSpotPrice * 50 / 10000), // 0.5% below market for immediate fill
359+
currentSpotPrice - ((currentSpotPrice * 50) / 10000), // 0.5% below market for immediate fill
349360
currentSpotPosition.size,
350361
true, // reduceOnly
351362
3, // IOC
@@ -390,7 +401,7 @@ contract CrestManager is Auth, ReentrancyGuard {
390401
CoreWriterLib.placeLimitOrder(
391402
currentPerpPosition.index,
392403
true, // isBuy (buy to close short)
393-
currentPerpPrice + (currentPerpPrice * 50 / 10000), // 0.5% above market for immediate fill
404+
currentPerpPrice + ((currentPerpPrice * 50) / 10000), // 0.5% above market for immediate fill
394405
currentPerpPosition.size,
395406
true, // reduceOnly
396407
3, // IOC
@@ -441,9 +452,9 @@ contract CrestManager is Auth, ReentrancyGuard {
441452
if (spotBalance.total > 0) {
442453
// Buy USDT0 with USDC
443454
CoreWriterLib.placeLimitOrder(
444-
USDT0_USDC_SPOT_INDEX,
455+
usdt0SpotIndex(),
445456
true, // buy USDT0
446-
PrecompileLib.spotPx(USDT0_USDC_SPOT_INDEX) + 10, // slight slippage
457+
PrecompileLib.spotPx(usdt0SpotIndex()) + 10, // slight slippage
447458
spotBalance.total,
448459
false,
449460
3, // IOC
@@ -452,9 +463,13 @@ contract CrestManager is Auth, ReentrancyGuard {
452463

453464
// Get USDT0 balance and bridge back
454465
PrecompileLib.SpotBalance memory usdt0Balance = PrecompileLib
455-
.spotBalance(address(this), USDT0_TOKEN_ID);
466+
.spotBalance(address(this), usdt0TokenId());
456467
if (usdt0Balance.total > 0) {
457-
CoreWriterLib.bridgeToEvm(USDT0_TOKEN_ID, usdt0Balance.total, false);
468+
CoreWriterLib.bridgeToEvm(
469+
usdt0TokenId(),
470+
usdt0Balance.total,
471+
false
472+
);
458473
}
459474
}
460475

packages/core/typescript/fetch-index.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
class HyperliquidAPI {
2+
baseUrl: string;
3+
constructor(baseUrl: string) {
4+
this.baseUrl = baseUrl;
5+
}
6+
27
async _requestInfo<T extends object>(request: { type: string }): Promise<T> {
3-
const response = await fetch('https://api.hyperliquid.xyz/info', {
8+
const response = await fetch(`${this.baseUrl}/info`, {
49
method: 'POST',
510
headers: { 'Content-Type': 'application/json' },
611
body: JSON.stringify(request),
@@ -59,25 +64,33 @@ class HyperliquidAPI {
5964
}
6065

6166
const main = async () => {
62-
const hl = new HyperliquidAPI();
63-
67+
console.log('===== MAINNET =====');
68+
const hl = new HyperliquidAPI('https://api.hyperliquid.xyz');
6469
{
6570
const indexes = await hl.getIndexesBySymbol('HYPE');
6671
console.log(indexes);
6772
}
68-
6973
{
7074
const indexes = await hl.getIndexesBySymbol('PURR');
7175
console.log(indexes);
7276
}
73-
7477
{
75-
const indexes = await hl.getIndexesBySymbol('BERA');
78+
const indexes = await hl.getIndexesBySymbol('USDT0');
7679
console.log(indexes);
7780
}
7881

82+
console.log('===== TESTNET =====');
83+
const testnet = new HyperliquidAPI('https://api.hyperliquid-testnet.xyz');
7984
{
80-
const indexes = await hl.getIndexesBySymbol('USDT0');
85+
const indexes = await testnet.getIndexesBySymbol('HYPE');
86+
console.log(indexes);
87+
}
88+
{
89+
const indexes = await testnet.getIndexesBySymbol('PURR');
90+
console.log(indexes);
91+
}
92+
{
93+
const indexes = await testnet.getIndexesBySymbol('TZERO'); // USDT0
8194
console.log(indexes);
8295
}
8396
};

0 commit comments

Comments
 (0)