Skip to content

Commit 3809b40

Browse files
committed
feat: add withdraw action
1 parent ebd571f commit 3809b40

File tree

16 files changed

+512
-46
lines changed

16 files changed

+512
-46
lines changed

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": "QmeB5SM3UxcFYKYNffhM4VMWXSnFoZPAH8iLAXTgYPPupp"
2+
"ipfsCid": "QmZCeMB39rcjAsUFFDJhz7ac4gn6HnPfwpdJRMDkGc5Z8K"
33
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { transferPrechecks } from './transfer';
44
export { spotTradePrechecks } from './spot';
55
export { perpTradePrechecks } from './perp';
66
export { cancelOrderPrechecks, cancelAllOrdersForSymbolPrechecks } from './cancel';
7+
export { withdrawPrechecks } from './withdraw';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { ethers } from 'ethers';
2+
import * as hyperliquid from '@nktkas/hyperliquid';
3+
4+
export type WithdrawPrechecksResult =
5+
| WithdrawPrechecksResultSuccess
6+
| WithdrawPrechecksResultFailure;
7+
8+
export interface WithdrawPrechecksResultSuccess {
9+
success: true;
10+
availableBalance: string;
11+
requiredBalance: string;
12+
}
13+
14+
export interface WithdrawPrechecksResultFailure {
15+
success: false;
16+
reason: string;
17+
availableBalance?: string;
18+
requiredBalance?: string;
19+
}
20+
21+
export interface WithdrawParams {
22+
amount: string;
23+
}
24+
25+
/**
26+
* Check if withdrawal from Hyperliquid to L1 can be executed
27+
*/
28+
export async function withdrawPrechecks({
29+
infoClient,
30+
ethAddress,
31+
params,
32+
}: {
33+
infoClient: hyperliquid.InfoClient;
34+
ethAddress: string;
35+
params: WithdrawParams;
36+
}): Promise<WithdrawPrechecksResult> {
37+
// Withdrawal comes from perp account
38+
const clearinghouseState = await infoClient.clearinghouseState({
39+
user: ethAddress,
40+
});
41+
42+
const availableUsdcBalance = ethers.utils.parseUnits(
43+
clearinghouseState.marginSummary.accountValue,
44+
6, // USDC has 6 decimals
45+
);
46+
47+
const requestedAmount = ethers.BigNumber.from(params.amount);
48+
const minimumWithdrawAmount = ethers.utils.parseUnits('1', 6); // 1 USDC minimum (withdrawal fee)
49+
50+
// Check if requested amount is at least 1 USDC
51+
if (requestedAmount.lt(minimumWithdrawAmount)) {
52+
return {
53+
success: false,
54+
reason: `Withdrawal amount must be at least 1 USDC due to withdrawal fee. Requested: ${ethers.utils.formatUnits(requestedAmount, 6)} USDC`,
55+
availableBalance: ethers.utils.formatUnits(availableUsdcBalance, 6),
56+
requiredBalance: ethers.utils.formatUnits(minimumWithdrawAmount, 6),
57+
};
58+
}
59+
60+
if (availableUsdcBalance.lt(requestedAmount)) {
61+
return {
62+
success: false,
63+
reason: `Insufficient perp balance for withdrawal. Available: ${ethers.utils.formatUnits(availableUsdcBalance, 6)} USDC, Requested: ${ethers.utils.formatUnits(requestedAmount, 6)} USDC`,
64+
availableBalance: ethers.utils.formatUnits(availableUsdcBalance, 6),
65+
requiredBalance: ethers.utils.formatUnits(requestedAmount, 6),
66+
};
67+
}
68+
69+
return {
70+
success: true,
71+
availableBalance: ethers.utils.formatUnits(availableUsdcBalance, 6),
72+
requiredBalance: ethers.utils.formatUnits(requestedAmount, 6),
73+
};
74+
}

packages/apps/ability-hyperliquid/src/lib/ability-helpers/cancel-order/cancel-all-orders.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CancelRequest, CancelResponse, parser } from '@nktkas/hyperliquid/api/e
44
import { signL1Action } from '@nktkas/hyperliquid/signing';
55

66
import { LitActionPkpEthersWallet } from '../lit-action-pkp-ethers-wallet';
7+
import { getHyperliquidNonce } from '../get-hyperliquid-nonce';
78

89
export type CancelAllOrdersResult = CancelAllOrdersResultSuccess | CancelAllOrdersResultFailure;
910

@@ -64,14 +65,7 @@ export async function cancelAllOrdersForSymbol({
6465
};
6566
}
6667

67-
// Generate deterministic nonce in runOnce
68-
const nonceResponse = await Lit.Actions.runOnce(
69-
{ waitForResponse: true, name: 'HyperLiquidCancelAllOrdersNonce' },
70-
async () => {
71-
return Date.now().toString();
72-
},
73-
);
74-
const nonce = parseInt(nonceResponse);
68+
const nonce = await getHyperliquidNonce();
7569

7670
// Construct cancel action for all orders
7771
const cancelAction = parser(CancelRequest.entries.action)({

packages/apps/ability-hyperliquid/src/lib/ability-helpers/cancel-order/cancel-order.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { signL1Action } from '@nktkas/hyperliquid/signing';
55
import { bigIntReplacer } from '@lit-protocol/vincent-ability-sdk';
66

77
import { LitActionPkpEthersWallet } from '../lit-action-pkp-ethers-wallet';
8+
import { getHyperliquidNonce } from '../get-hyperliquid-nonce';
89

910
export interface CancelSpotOrderParams {
1011
symbol: string; // e.g., "PURR/USDC"
@@ -47,15 +48,7 @@ export async function cancelOrder({
4748

4849
// Create PKP wallet
4950
const pkpWallet = new LitActionPkpEthersWallet(pkpPublicKey);
50-
51-
// Generate deterministic nonce in runOnce
52-
const nonceResponse = await Lit.Actions.runOnce(
53-
{ waitForResponse: true, name: 'HyperLiquidCancelOrderNonce' },
54-
async () => {
55-
return Date.now().toString();
56-
},
57-
);
58-
const nonce = parseInt(nonceResponse);
51+
const nonce = await getHyperliquidNonce();
5952

6053
// Construct cancel action
6154
const cancelAction = parser(CancelRequest.entries.action)({

packages/apps/ability-hyperliquid/src/lib/ability-helpers/execute-perp-order.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { signL1Action } from '@nktkas/hyperliquid/signing';
1010
import { bigIntReplacer } from '@lit-protocol/vincent-ability-sdk';
1111

1212
import { LitActionPkpEthersWallet } from './lit-action-pkp-ethers-wallet';
13+
import { getHyperliquidNonce } from './get-hyperliquid-nonce';
1314

1415
export type TimeInForce = 'Gtc' | 'Ioc' | 'Alo';
1516

@@ -122,14 +123,7 @@ export async function executePerpOrder({
122123
);
123124
}
124125

125-
// Generate deterministic nonce for order in runOnce
126-
const nonceResponse = await Lit.Actions.runOnce(
127-
{ waitForResponse: true, name: 'HyperLiquidPerpOrderNonce' },
128-
async () => {
129-
return Date.now().toString();
130-
},
131-
);
132-
const nonce = parseInt(nonceResponse);
126+
const nonce = await getHyperliquidNonce();
133127

134128
// Determine order type configuration
135129
const orderType = params.orderType || { type: 'limit', tif: 'Gtc' };

packages/apps/ability-hyperliquid/src/lib/ability-helpers/execute-spot-order.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { signL1Action } from '@nktkas/hyperliquid/signing';
55
import { bigIntReplacer } from '@lit-protocol/vincent-ability-sdk';
66

77
import { LitActionPkpEthersWallet } from './lit-action-pkp-ethers-wallet';
8+
import { getHyperliquidNonce } from './get-hyperliquid-nonce';
89

910
export type TimeInForce = 'Gtc' | 'Ioc' | 'Alo';
1011

@@ -58,15 +59,7 @@ export async function executeSpotOrder({
5859

5960
// Create PKP wallet
6061
const pkpWallet = new LitActionPkpEthersWallet(pkpPublicKey);
61-
62-
// Generate deterministic nonce in runOnce
63-
const nonceResponse = await Lit.Actions.runOnce(
64-
{ waitForResponse: true, name: 'HyperLiquidSpotOrderNonce' },
65-
async () => {
66-
return Date.now().toString();
67-
},
68-
);
69-
const nonce = parseInt(nonceResponse);
62+
const nonce = await getHyperliquidNonce();
7063

7164
// Determine order type configuration
7265
const orderType = params.orderType || { type: 'limit', tif: 'Gtc' };
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export async function getHyperliquidNonce() {
2+
const nonceResponse = await Lit.Actions.runOnce(
3+
{ waitForResponse: true, name: 'HyperLiquidWithdrawNonce' },
4+
async () => {
5+
return Date.now().toString();
6+
},
7+
);
8+
return parseInt(nonceResponse);
9+
}

packages/apps/ability-hyperliquid/src/lib/ability-helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { sendDepositTx } from './send-deposit-tx';
55
export { transferUsdcTo } from './transfer-usdc-to';
66
export { executeSpotOrder } from './execute-spot-order';
77
export { executePerpOrder } from './execute-perp-order';
8+
export { withdrawUsdc } from './withdraw-usdc';

0 commit comments

Comments
 (0)