|
| 1 | +import { |
| 2 | + delegator, |
| 3 | + delegatee, |
| 4 | + funder, |
| 5 | + appManager, |
| 6 | + ensureUnexpiredCapacityToken, |
| 7 | + getChainHelpers, |
| 8 | + getEnv, |
| 9 | + type PkpInfo, |
| 10 | +} from '@lit-protocol/vincent-e2e-test-utils'; |
| 11 | +import { type PermissionData } from '@lit-protocol/vincent-contracts-sdk'; |
| 12 | +import { disconnectVincentAbilityClients } from '@lit-protocol/vincent-app-sdk/abilityClient'; |
| 13 | +import * as util from 'node:util'; |
| 14 | +import { z } from 'zod'; |
| 15 | +import { ethers, type Wallet, type providers } from 'ethers'; |
| 16 | +import * as hyperliquid from '@nktkas/hyperliquid'; |
| 17 | +import { ERC20_ABI } from '@lit-protocol/vincent-ability-sdk'; |
| 18 | + |
| 19 | +import { bundledVincentAbility as hyperliquidBundledAbility } from '../../src'; |
| 20 | + |
| 21 | +// USDC contract addresses on Arbitrum |
| 22 | +const ARBITRUM_USDC_ADDRESS_MAINNET = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; |
| 23 | +const ARBITRUM_USDC_ADDRESS_TESTNET = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'; |
| 24 | + |
| 25 | +// Extend Jest timeout to 4 minutes |
| 26 | +jest.setTimeout(240000); |
| 27 | + |
| 28 | +describe('Hyperliquid Ability E2E Balance Tests', () => { |
| 29 | + const ENV = getEnv({ |
| 30 | + ARBITRUM_RPC_URL: z.string(), |
| 31 | + }); |
| 32 | + const USE_TESTNET = true; |
| 33 | + |
| 34 | + let agentPkpInfo: PkpInfo; |
| 35 | + let wallets: { |
| 36 | + appDelegatee: Wallet; |
| 37 | + funder: Wallet; |
| 38 | + appManager: Wallet; |
| 39 | + agentWalletOwner: Wallet; |
| 40 | + }; |
| 41 | + let transport: hyperliquid.HttpTransport; |
| 42 | + let infoClient: hyperliquid.InfoClient; |
| 43 | + let arbitrumRpcProvider: providers.JsonRpcProvider; |
| 44 | + |
| 45 | + beforeAll(async () => { |
| 46 | + await funder.checkFunderBalance(); |
| 47 | + await delegatee.ensureAppDelegateeFunded(); |
| 48 | + await appManager.ensureAppManagerFunded(); |
| 49 | + |
| 50 | + const chainHelpers = await getChainHelpers(); |
| 51 | + wallets = chainHelpers.wallets; |
| 52 | + |
| 53 | + await ensureUnexpiredCapacityToken(wallets.appDelegatee); |
| 54 | + |
| 55 | + const PERMISSION_DATA: PermissionData = { |
| 56 | + // Hyperliquid Ability has no policies |
| 57 | + [hyperliquidBundledAbility.ipfsCid]: {}, |
| 58 | + }; |
| 59 | + |
| 60 | + const abilityIpfsCids: string[] = Object.keys(PERMISSION_DATA); |
| 61 | + const abilityPolicies: string[][] = abilityIpfsCids.map((abilityIpfsCid) => { |
| 62 | + return Object.keys(PERMISSION_DATA[abilityIpfsCid]); |
| 63 | + }); |
| 64 | + |
| 65 | + // If an app exists for the delegatee, we will create a new app version with the new ipfs cids |
| 66 | + // Otherwise, we will create an app w/ version 1 appVersion with the new ipfs cids |
| 67 | + const existingApp = await delegatee.getAppInfo(); |
| 68 | + console.log('[beforeAll] existingApp', existingApp); |
| 69 | + let appId: number; |
| 70 | + let appVersion: number; |
| 71 | + if (!existingApp) { |
| 72 | + console.log('[beforeAll] No existing app, registering new app'); |
| 73 | + const newApp = await appManager.registerNewApp({ abilityIpfsCids, abilityPolicies }); |
| 74 | + appId = newApp.appId; |
| 75 | + appVersion = newApp.appVersion; |
| 76 | + } else { |
| 77 | + console.log('[beforeAll] Existing app, registering new app version'); |
| 78 | + const newAppVersion = await appManager.registerNewAppVersion({ |
| 79 | + abilityIpfsCids, |
| 80 | + abilityPolicies, |
| 81 | + }); |
| 82 | + appId = existingApp.appId; |
| 83 | + appVersion = newAppVersion.appVersion; |
| 84 | + } |
| 85 | + |
| 86 | + agentPkpInfo = await delegator.getFundedAgentPkp(); |
| 87 | + |
| 88 | + await delegator.permitAppVersionForAgentWalletPkp({ |
| 89 | + permissionData: PERMISSION_DATA, |
| 90 | + appId, |
| 91 | + appVersion, |
| 92 | + agentPkpInfo, |
| 93 | + }); |
| 94 | + |
| 95 | + await delegator.addPermissionForAbilities( |
| 96 | + wallets.agentWalletOwner, |
| 97 | + agentPkpInfo.tokenId, |
| 98 | + abilityIpfsCids, |
| 99 | + ); |
| 100 | + |
| 101 | + transport = new hyperliquid.HttpTransport({ isTestnet: USE_TESTNET }); |
| 102 | + infoClient = new hyperliquid.InfoClient({ transport }); |
| 103 | + arbitrumRpcProvider = new ethers.providers.JsonRpcProvider(ENV.ARBITRUM_RPC_URL); |
| 104 | + }); |
| 105 | + |
| 106 | + afterAll(async () => { |
| 107 | + await disconnectVincentAbilityClients(); |
| 108 | + }); |
| 109 | + |
| 110 | + describe('Fetch Spot and Perp Balances', () => { |
| 111 | + it('should fetch the spot balance of the agent wallet PKP ETH address', async () => { |
| 112 | + const spotState = await infoClient.spotClearinghouseState({ |
| 113 | + user: agentPkpInfo.ethAddress as `0x${string}`, |
| 114 | + }); |
| 115 | + |
| 116 | + expect(spotState).toBeDefined(); |
| 117 | + console.log('[Spot Balance] Agent Wallet PKP ETH Address:', agentPkpInfo.ethAddress); |
| 118 | + console.log('[Spot Balance] Full spot state:', util.inspect(spotState, { depth: 10 })); |
| 119 | + |
| 120 | + if (spotState.balances && spotState.balances.length > 0) { |
| 121 | + console.log('[Spot Balance] Balances:'); |
| 122 | + spotState.balances.forEach((balance) => { |
| 123 | + console.log(` ${balance.coin}: total=${balance.total}, hold=${balance.hold}`); |
| 124 | + }); |
| 125 | + |
| 126 | + // Check for USDC balance specifically |
| 127 | + const usdcBalance = spotState.balances.find((b) => b.coin === 'USDC'); |
| 128 | + if (usdcBalance) { |
| 129 | + console.log('[Spot Balance] USDC Balance:', usdcBalance.total); |
| 130 | + expect(usdcBalance.total).toBeDefined(); |
| 131 | + } |
| 132 | + } else { |
| 133 | + console.log('[Spot Balance] No spot balances found'); |
| 134 | + } |
| 135 | + }); |
| 136 | + |
| 137 | + it('should fetch the perpetual balance of the agent wallet PKP ETH address', async () => { |
| 138 | + const perpState = await infoClient.clearinghouseState({ |
| 139 | + user: agentPkpInfo.ethAddress as `0x${string}`, |
| 140 | + }); |
| 141 | + |
| 142 | + expect(perpState).toBeDefined(); |
| 143 | + console.log('[Perp Balance] Agent Wallet PKP ETH Address:', agentPkpInfo.ethAddress); |
| 144 | + console.log('[Perp Balance] Full perp state:', util.inspect(perpState, { depth: 10 })); |
| 145 | + |
| 146 | + // Check margin summary |
| 147 | + if (perpState.marginSummary) { |
| 148 | + console.log('[Perp Balance] Margin Summary:'); |
| 149 | + console.log(` Account Value: ${perpState.marginSummary.accountValue}`); |
| 150 | + console.log(` Total Margin Used: ${perpState.marginSummary.totalMarginUsed}`); |
| 151 | + console.log(` Total NTL Position: ${perpState.marginSummary.totalNtlPos}`); |
| 152 | + console.log(` Total Raw USD: ${perpState.marginSummary.totalRawUsd}`); |
| 153 | + |
| 154 | + expect(perpState.marginSummary.accountValue).toBeDefined(); |
| 155 | + } |
| 156 | + |
| 157 | + // Check asset positions |
| 158 | + if (perpState.assetPositions && perpState.assetPositions.length > 0) { |
| 159 | + console.log('[Perp Balance] Asset Positions:'); |
| 160 | + perpState.assetPositions.forEach((assetPosition) => { |
| 161 | + const pos = assetPosition.position; |
| 162 | + console.log(` ${pos.coin}:`); |
| 163 | + console.log(` Position Size (szi): ${pos.szi}`); |
| 164 | + console.log(` Position Value: ${pos.positionValue}`); |
| 165 | + console.log(` Unrealized PnL: ${pos.unrealizedPnl}`); |
| 166 | + console.log(` Entry Price: ${pos.entryPx || 'N/A'}`); |
| 167 | + }); |
| 168 | + } else { |
| 169 | + console.log('[Perp Balance] No open perp positions found'); |
| 170 | + } |
| 171 | + |
| 172 | + // Check if there's any cross margin summary |
| 173 | + if (perpState.crossMarginSummary) { |
| 174 | + console.log('[Perp Balance] Cross Margin Summary:'); |
| 175 | + console.log(` Account Value: ${perpState.crossMarginSummary.accountValue}`); |
| 176 | + console.log(` Total Margin Used: ${perpState.crossMarginSummary.totalMarginUsed}`); |
| 177 | + } |
| 178 | + }); |
| 179 | + }); |
| 180 | + |
| 181 | + describe('Fetch Arbitrum Balances', () => { |
| 182 | + it('should fetch the Arbitrum ETH balance of the agent wallet PKP ETH address', async () => { |
| 183 | + const ethBalance = await arbitrumRpcProvider.getBalance(agentPkpInfo.ethAddress); |
| 184 | + |
| 185 | + expect(ethBalance).toBeDefined(); |
| 186 | + console.log('[Arbitrum ETH Balance] Agent Wallet PKP ETH Address:', agentPkpInfo.ethAddress); |
| 187 | + console.log('[Arbitrum ETH Balance] Raw balance (wei):', ethBalance.toString()); |
| 188 | + console.log( |
| 189 | + '[Arbitrum ETH Balance] Formatted balance (ETH):', |
| 190 | + ethers.utils.formatEther(ethBalance), |
| 191 | + ); |
| 192 | + }); |
| 193 | + |
| 194 | + it('should fetch the Arbitrum USDC balance of the agent wallet PKP ETH address', async () => { |
| 195 | + const usdcAddress = USE_TESTNET |
| 196 | + ? ARBITRUM_USDC_ADDRESS_TESTNET |
| 197 | + : ARBITRUM_USDC_ADDRESS_MAINNET; |
| 198 | + |
| 199 | + console.log( |
| 200 | + `[Arbitrum USDC Balance] Using ${USE_TESTNET ? 'testnet' : 'mainnet'} USDC address:`, |
| 201 | + usdcAddress, |
| 202 | + ); |
| 203 | + |
| 204 | + const usdcContract = new ethers.Contract(usdcAddress, ERC20_ABI, arbitrumRpcProvider); |
| 205 | + const usdcBalance = await usdcContract.balanceOf(agentPkpInfo.ethAddress); |
| 206 | + |
| 207 | + console.log('[Arbitrum USDC Balance] Agent Wallet PKP ETH Address:', agentPkpInfo.ethAddress); |
| 208 | + console.log('[Arbitrum USDC Balance] Raw balance:', usdcBalance.toString()); |
| 209 | + console.log( |
| 210 | + '[Arbitrum USDC Balance] Formatted balance (USDC):', |
| 211 | + ethers.utils.formatUnits(usdcBalance, 6), |
| 212 | + ); |
| 213 | + }); |
| 214 | + }); |
| 215 | +}); |
0 commit comments