Skip to content

Commit 687482d

Browse files
committed
chore: add e2e testing docs
1 parent dfcecd2 commit 687482d

File tree

17 files changed

+1965
-44
lines changed

17 files changed

+1965
-44
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Specifying this ENV in the root of the monorepo to reuse across Abilities will also work
2+
PINATA_JWT=
3+
4+
ARBITRUM_RPC_URL=
5+
6+
TEST_FUNDER_PRIVATE_KEY=
7+
8+
TEST_APP_MANAGER_PRIVATE_KEY=
9+
10+
TEST_APP_DELEGATEE_PRIVATE_KEY=
11+
12+
TEST_AGENT_WALLET_PKP_OWNER_PRIVATE_KEY=

packages/apps/ability-hyperliquid/README.md

Lines changed: 801 additions & 1 deletion
Large diffs are not rendered by default.

packages/apps/ability-hyperliquid/src/generated/lit-action.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"ipfsCid": "QmRXDcNv2pfpPqSDjJM86u75povvZwRaLr2Azx9F1hnsWF"
2+
"ipfsCid": "QmeB5SM3UxcFYKYNffhM4VMWXSnFoZPAH8iLAXTgYPPupp"
33
}

packages/apps/ability-hyperliquid/src/lib/ability-checks/deposit.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ethers } from 'ethers';
22
import { ERC20_ABI } from '@lit-protocol/vincent-ability-sdk';
33

4-
import { ARBITRUM_USDC_ADDRESS_MAINNET, ARBITRUM_USDC_ADDRESS_TESTNET } from '../types';
4+
import { ARBITRUM_USDC_ADDRESS_MAINNET } from '../types';
55

66
export type DepositPrechecksResult = DepositPrechecksResultSuccess | DepositPrechecksResultFailure;
77

@@ -23,15 +23,13 @@ export const depositPrechecks = async ({
2323
provider,
2424
agentWalletPkpEthAddress,
2525
depositAmountInMicroUsdc,
26-
useTestnet = false,
2726
}: {
2827
provider: ethers.providers.Provider;
2928
agentWalletPkpEthAddress: string;
3029
depositAmountInMicroUsdc: string;
3130
useTestnet?: boolean;
3231
}): Promise<DepositPrechecksResult> => {
33-
const usdcAddress = useTestnet ? ARBITRUM_USDC_ADDRESS_TESTNET : ARBITRUM_USDC_ADDRESS_MAINNET;
34-
const usdcContract = new ethers.Contract(usdcAddress, ERC20_ABI, provider);
32+
const usdcContract = new ethers.Contract(ARBITRUM_USDC_ADDRESS_MAINNET, ERC20_ABI, provider);
3533

3634
const ethBalance = await provider.getBalance(agentWalletPkpEthAddress);
3735
if (ethBalance.eq(0n)) {

packages/apps/ability-hyperliquid/src/lib/types.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,4 @@ export enum OrderType {
3535

3636
export const HYPERLIQUID_BRIDGE_ADDRESS_MAINNET = '0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7';
3737

38-
export const HYPERLIQUID_BRIDGE_ADDRESS_TESTNET = '0xedb6e5c456b7cca2eb1e5c7007f4ab80426cd20f';
39-
4038
export const ARBITRUM_USDC_ADDRESS_MAINNET = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
41-
42-
export const ARBITRUM_USDC_ADDRESS_TESTNET = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d';

packages/apps/ability-hyperliquid/src/lib/vincent-ability.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ import * as hyperliquid from '@nktkas/hyperliquid';
99

1010
import {
1111
HYPERLIQUID_BRIDGE_ADDRESS_MAINNET,
12-
HYPERLIQUID_BRIDGE_ADDRESS_TESTNET,
1312
ARBITRUM_USDC_ADDRESS_MAINNET,
14-
ARBITRUM_USDC_ADDRESS_TESTNET,
1513
HyperliquidAction,
1614
} from './types';
1715
import {
@@ -68,6 +66,14 @@ export const vincentAbility = createVincentAbility({
6866
});
6967

7068
if (action === HyperliquidAction.DEPOSIT) {
69+
if (useTestnet) {
70+
return fail({
71+
action,
72+
reason:
73+
'Deposit is not supported on Hyperliquid testnet. Please refer to these docs for getting testnet USDC: https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/testnet-faucet',
74+
});
75+
}
76+
7177
if (!arbitrumRpcUrl) {
7278
return fail({ action, reason: 'Arbitrum RPC URL is required for precheck' });
7379
}
@@ -245,40 +251,37 @@ export const vincentAbility = createVincentAbility({
245251
try {
246252
switch (action) {
247253
case HyperliquidAction.DEPOSIT: {
254+
if (useTestnet) {
255+
return fail({
256+
action,
257+
reason:
258+
'Deposit is not supported on Hyperliquid testnet. Please refer to these docs for getting testnet USDC: https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/testnet-faucet',
259+
});
260+
}
261+
248262
if (!abilityParams.deposit) {
249263
return fail({ action, reason: 'Deposit parameters are required' });
250264
}
251265

252266
// Get Arbitrum RPC URL
253267
const rpcUrl = await Lit.Actions.getRpcUrl({
254-
chain: useTestnet ? 'arbitrumSepolia' : 'arbitrum',
268+
chain: 'arbitrum',
255269
});
256270

257-
// Select addresses based on network
258-
const bridgeAddress = ethers.utils.getAddress(
259-
useTestnet ? HYPERLIQUID_BRIDGE_ADDRESS_TESTNET : HYPERLIQUID_BRIDGE_ADDRESS_MAINNET,
260-
);
261-
const usdcAddress = ethers.utils.getAddress(
262-
useTestnet ? ARBITRUM_USDC_ADDRESS_TESTNET : ARBITRUM_USDC_ADDRESS_MAINNET,
263-
);
264-
const chainId = useTestnet
265-
? 421614 // Arbitrum Sepolia testnet
266-
: 42161; // Arbitrum mainnet
267-
268271
// Encode ERC20 transfer function call data
269272
const iface = new ethers.utils.Interface(ERC20_ABI);
270273
const calldata = iface.encodeFunctionData('transfer', [
271-
bridgeAddress,
274+
HYPERLIQUID_BRIDGE_ADDRESS_MAINNET,
272275
abilityParams.deposit.amount,
273276
]);
274277

275278
// Send deposit transaction using helper
276279
const txHash = await sendDepositTx({
277280
rpcUrl,
278-
chainId,
281+
chainId: 42161, // Arbitrum mainnet
279282
pkpEthAddress: delegatorPkpInfo.ethAddress,
280283
pkpPublicKey: delegatorPkpInfo.publicKey,
281-
to: usdcAddress,
284+
to: ARBITRUM_USDC_ADDRESS_MAINNET,
282285
value: '0x0',
283286
calldata,
284287
});
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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+
});

packages/apps/ability-hyperliquid/test/e2e/deposit.spec.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ describe('Hyperliquid Ability E2E Deposit Tests', () => {
2727
const ENV = getEnv({
2828
ARBITRUM_RPC_URL: z.string(),
2929
});
30-
const USDC_DEPOSIT_AMOUNT = '1000000';
31-
const USE_TESTNET = true;
30+
const USDC_DEPOSIT_AMOUNT = '15000000'; // 15 USDC
3231

3332
let agentPkpInfo: PkpInfo;
3433
let wallets: {
@@ -111,7 +110,6 @@ describe('Hyperliquid Ability E2E Deposit Tests', () => {
111110
const precheckResult = await hyperliquidAbilityClient.precheck(
112111
{
113112
action: 'deposit',
114-
useTestnet: USE_TESTNET,
115113
deposit: {
116114
amount: USDC_DEPOSIT_AMOUNT,
117115
},
@@ -142,7 +140,6 @@ describe('Hyperliquid Ability E2E Deposit Tests', () => {
142140
const executeResult = await hyperliquidAbilityClient.execute(
143141
{
144142
action: 'deposit',
145-
useTestnet: USE_TESTNET,
146143
deposit: {
147144
amount: USDC_DEPOSIT_AMOUNT,
148145
},
@@ -178,7 +175,7 @@ describe('Hyperliquid Ability E2E Deposit Tests', () => {
178175
// NOTE It takes about a minute for the deposit to be reflected in the portfolio.
179176
// This test assumes a previous deposit has been made by the Agent Wallet PKP into the HyperLiquid bridge.
180177
it('should verify the Agent Wallet PKP has funds in the HyperLiquid portfolio', async () => {
181-
const transport = new hyperliquid.HttpTransport({ isTestnet: USE_TESTNET });
178+
const transport = new hyperliquid.HttpTransport({ isTestnet: false });
182179
const infoClient = new hyperliquid.InfoClient({ transport });
183180

184181
// Check clearinghouse state which includes account balance

0 commit comments

Comments
 (0)