Hyperliquid: Margin Guardian
Auto-protects your Hyperliquid account from liquidation. Monitors margin health on a schedule — if health drops below your threshold, automatically deposits USDC from Arbitrum. Sends urgent alerts when margin is critical but no USDC is available.
Trigger: Cronjob → Actions: Analyze Margin Health, If, Hyperliquid Deposit, Hyperliquid Get Account, Sim Dune Get Single Token Balance, Log, Notification, Notification, If
Hyperliquid: Margin Guardian
Created by
B3 Team
1.
Root
2.
Get Account
3.
Get Usdc Balance
4.
Analyze Margin Health
5.
Should Topup
About
Auto-protects your Hyperliquid account from liquidation. Monitors margin health on a schedule — if health drops below your threshold, automatically deposits USDC from Arbitrum. Sends urgent alerts when margin is critical but no USDC is available.
Preview
Analyze Margin Health
Action
Code:function transform(input) {
const acct = input.account;
const positions = acct.positions || [];
const accountValue = parseFloat(acct.accountValue) || 0;
const maintenanceMargin = parseFloat(acct.crossMaintenanceMarginUsed) || 0;
const withdrawable = parseFloat(acct.withdrawable) || 0;
if (positions.length === 0 || accountValue === 0) {
return { shouldTopUp: false, isDanger: false, message: '' };
}
const marginHealth = maintenanceMargin > 0 ? accountValue / maintenanceMargin : 999;
let closestLiqPct = 1;
let riskiestCoin = '';
let riskiestNotional = 0;
for (const p of positions) {
const mark = parseFloat(p.markPrice);
const liq = parseFloat(p.liquidationPrice);
if (!mark || !liq) continue;
const dist = Math.abs(mark - liq) / mark;
if (dist < closestLiqPct) {
closestLiqPct = dist;
riskiestCoin = p.coin;
riskiestNotional = Math.abs(parseFloat(p.size)) * mark;
}
}
const healthThreshold = input.healthThreshold;
const targetHealth = input.targetHealth;
const atRisk = marginHealth < healthThreshold;
if (!atRisk) {
return { shouldTopUp: false, isDanger: false, message: '' };
}
const neededDeposit = Math.max(0, targetHealth * maintenanceMargin - accountValue);
const neededRounded = Math.ceil(neededDeposit);
const bal = input.sourceBalance;
const usdcRaw = bal.balances && bal.balances[0] ? bal.balances[0].amount : '0';
const usdcDecimals = bal.balances && bal.balances[0] ? bal.balances[0].decimals : 6;
const usdcAvailable = parseFloat(usdcRaw) / Math.pow(10, usdcDecimals);
const depositAmount = Math.min(neededRounded, Math.floor(usdcAvailable));
const canDeposit = depositAmount >= 1;
const postHealth = canDeposit
? (accountValue + depositAmount) / maintenanceMargin
: marginHealth;
const fullyCovered = canDeposit && depositAmount >= neededRounded;
const healthStr = marginHealth.toFixed(2);
const liqPct = (closestLiqPct * 100).toFixed(1);
const postStr = postHealth.toFixed(2);
if (canDeposit) {
const coverageNote = fullyCovered
? 'Health fully restored to ' + postStr + 'x.'
: 'Partial restore to ' + postStr + 'x (needed $' + neededRounded + ', had $' + Math.floor(usdcAvailable) + '). Add more USDC to Arbitrum.';
return {
shouldTopUp: true,
isDanger: false,
depositAmount: depositAmount.toString(),
message: 'MARGIN GUARDIAN AUTO TOP-UP\n\n' +
'Account Health (Before)\n' +
'Margin health: ' + healthStr + 'x (liquidation at 1.0x)\n' +
'Closest liq: ' + riskiestCoin + ' at ' + liqPct + '% away ($' + riskiestNotional.toFixed(0) + ' notional)\n' +
'Account value: $' + accountValue.toFixed(2) + '\n' +
'Maintenance margin: $' + maintenanceMargin.toFixed(2) + '\n' +
'Withdrawable: $' + withdrawable.toFixed(2) + '\n\n' +
'Action: Depositing $' + depositAmount + ' USDC from Arbitrum.\n' +
coverageNote
};
}
return {
shouldTopUp: false,
isDanger: true,
depositAmount: '0',
message: 'MARGIN CRITICAL — NO USDC AVAILABLE\n\n' +
'Account Health\n' +
'Margin health: ' + healthStr + 'x (liquidation at 1.0x)\n' +
'Closest liq: ' + riskiestCoin + ' at ' + liqPct + '% away ($' + riskiestNotional.toFixed(0) + ' notional)\n' +
'Account value: $' + accountValue.toFixed(2) + '\n' +
'Maintenance margin: $' + maintenanceMargin.toFixed(2) + '\n' +
'Withdrawable: $' + withdrawable.toFixed(2) + '\n\n' +
'Needed: $' + neededRounded + ' to restore health to ' + targetHealth.toFixed(1) + 'x\n' +
'USDC on Arbitrum: $' + usdcAvailable.toFixed(2) + '\n\n' +
'Action required: Deposit funds or reduce positions NOW.'
};
}
Input:{"account":{"accountValue":"{{get_account.result.accountValue}}","crossMaintenanceMarginUsed":"{{get_account.result.crossMaintenanceMarginUsed}}","positions":"{{get_account.result.positions}}","totalMarginUsed":"{{get_account.result.totalMarginUsed}}","withdrawable":"{{get_account.result.withdrawable}}"},"healthThreshold":"{{$props.healthThreshold}}","sourceBalance":{"balances":"{{get_usdc_balance.result.balances}}"},"targetHealth":"{{$props.targetHealth}}"}
check_danger
Action
Condition:{"$and":[{"{{analyze_risk.result.output.isDanger}}":{"$eq":true}}]}
do_deposit
Action
Amount:Analyze Margin Health:output.depositAmount
Chain:
Arbitrum
Arbitrumget_account
Action
Address:Prop: walletAddress
get_usdc_balance
Action
Address:get_account.connector.address
Chain:
Arbitrum
ArbitrumToken:
0xaf88...5831
log_ok
Action
Margin healthy - no action needed
notify_danger
Message configured
{{analyze_risk.result.output.message}}
notify_topup
Message configured
{{analyze_risk.result.output.message}}
root
Action
Schedule:
DTSTART:20260101T000000Z
RRULE:FREQ=MINUTELY;INTERVAL={{$props.cadence}}
should_topup
Action
Condition:{"$and":[{"{{analyze_risk.result.output.shouldTopUp}}":{"$eq":true}}]}
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Loading chat...