Risk Controls

Understand how Atract's automated risk management protects vault capital.

Overview

Atract implements multi-layered risk controls to protect investor capital when traders use vault funding. These automated systems monitor every trade and can pause trading if risk limits are breached.

Risk Control Architecture

Two-Layer Protection

Layer 1: Smart Contract (On-Chain)

  • Enforces drawdown limits

  • Pauses vault when limits breached

  • Cannot be bypassed or disabled

Layer 2: Oracle System (Real-time Monitoring)

  • Monitors Hyperliquid vault balance

  • Checks balance every 60 seconds

  • Triggers smart contract actions

Result: Vault capital is protected through continuous monitoring and automatic enforcement.

Drawdown Limits

Max Drawdown

What it is: The maximum percentage loss from the vault's peak balance.

How it works:

Peak Balance: $100,000
Max Drawdown: 20%
Pause Trigger: $80,000

When the vault is paused:

  • Vault.paused() returns true

  • No new trades or position increases allowed

  • Closing positions is still allowed

Recovery: If balance improves above the limit (due to market recovery or partial closes), the vault remains paused until manually unpaused by the trader.

Daily Drawdown

What it is: The maximum percentage loss within a single 24-hour period.

How it works:

Balance at Start of Day: $100,000
Daily Drawdown: 5%
Pause Trigger: $95,000

Reset: Daily drawdown resets every 24 hours at midnight UTC.

Purpose: Prevents a single catastrophic day from wiping out capital.

Combined Example

Vault Parameters:
Initial Balance: $100,000
Max Drawdown: 20%
Daily Drawdown: 5%

Scenario 1: Slow decline
Day 1: $98,000 (2% down) - OK
Day 2: $96,000 (2% more) - OK
Day 3: $94,000 (2% more) - OK
...eventually...
Day 10: $80,000 (20% total) - PAUSED (max drawdown hit)

Scenario 2: Bad day
Start: $100,000
After trades: $94,500 (5.5% down) - PAUSED (daily drawdown hit)

Oracle Balance Sync

How It Works

The oracle system continuously monitors vaults on Hyperliquid:

  1. Poll Hyperliquid: Every 60 seconds

    • Fetch account value from getAccountState(vaultAddress)

    • Convert to USDC equivalent

  2. Update On-Chain: Write to vault contract

    • Call vault.updateBalance(newBalance)

    • Update lastBalanceUpdate timestamp

  3. Check Limits: Smart contract checks

    • Is balance below max drawdown threshold?

    • Is balance below daily drawdown threshold?

    • If yes to either: vault.pause() is called automatically

Result: Risk limits are enforced within ~60 seconds of breach.

Balance Freshness

Critical for safety:

Atract checks balance age before every vault trade:

Hard Block (>10 minutes):

  • Trade is rejected immediately

  • Error: "Balance data too stale"

  • Reason: Cannot trust old data for risk decisions

Warning (5-10 minutes):

  • Trade allowed but warning logged

  • May indicate oracle issues

Fresh (<5 minutes):

  • Trade proceeds normally

Why this matters: If the oracle stops updating, we cannot know the true vault balance. Trading with stale data could violate risk limits unknowingly.

Vault Pause Behavior

What Happens When Paused

Automatically:

  • Vault status changes to PAUSED

  • Vault.paused() returns true

  • Event emitted: VaultPaused(reason)

Allowed Actions:

  • View positions

  • Close positions (reduce-only orders)

  • Withdraw remaining balance (if vault is closed)

Blocked Actions:

  • Opening new positions

  • Increasing existing positions

  • Any trade that adds risk

Unpause Mechanism

Option 1: Balance Recovery If market moves in your favor and balance recovers above the limit:

  • Vault remains paused (safety feature)

  • Trader must manually unpause via vault dashboard

  • Investor can verify recovery before trading resumes

Option 2: Manual Unpause Trader can request unpause:

  • Only if balance is above thresholds

  • Investor may be notified (future feature)

Option 3: Vault Closure If recovery is unlikely:

  • Close all positions

  • Trigger vault closure

  • Distribute remaining capital per profit-sharing agreement

Pre-Trade Checks

Before every vault trade, Atract performs:

1. Vault Status Check

const vaultRecord = await db.vault.findUnique({ address });
if (vaultRecord.status !== 'ACTIVE') {
  reject('Vault not active');
}

2. Contract Deployment Check

const code = await provider.getCode(vaultAddress);
if (code === '0x') {
  reject('Contract not deployed');
}

3. On-Chain State Check

const paused = await vaultContract.paused();
const closed = await vaultContract.closed();
const deadline = await vaultContract.dealDeadline();

if (paused || closed || Date.now() > deadline) {
  reject('Vault cannot accept trades');
}

4. Balance Freshness Check

const ageSeconds = now - vaultRecord.lastBalanceUpdate;
if (ageSeconds > 600) {  // 10 minutes
  reject('Balance too stale');
}

5. Hyperliquid Balance Check

const hlState = await HLClient.getAccountState(vaultAddress);
const availableBalance = hlState.marginSummary.accountValue;

if (orderSize > availableBalance * 0.8) {
  reject('Insufficient balance');
}

All checks must pass before order is submitted to Hyperliquid.

Post-Trade Verification

After trade execution, Atract checks for race conditions:

Race Condition Detection

What's the risk: Vault could be paused/closed between the pre-check and trade execution.

How we detect:

// After trade executes
const postPaused = await vaultContract.paused();
if (postPaused && !prePaused) {
  // Vault was paused during execution
  logger.error('RACE_CONDITION_DETECTED', { orderId, vault });
}

What happens:

  • Trade cannot be reversed (already executed on Hyperliquid)

  • Event logged for transparency

  • Investor/trader notified

  • Future trades blocked

Why this matters: Oracle update might pause vault at the exact moment trade is executing. Detection ensures everyone knows what happened.

Deal Deadline

Time-Based Protection

Every vault has a deal deadline:

Proposal Duration: 30 days
Start: 2025-01-01
Deal Deadline: 2025-01-31

After deadline:

  • Vault automatically becomes eligible for closure

  • No new trades allowed

  • Parties must settle and close vault

Purpose:

  • Ensures vaults don't run indefinitely

  • Forces periodic settlement

  • Protects both investors and traders

Deadline Checks

Checked on every trade:

const dealDeadline = await vaultContract.dealDeadline();
if (Date.now() > dealDeadline) {
  reject('Deal deadline passed - vault must be closed');
}

Emergency Procedures

Trader Actions

If vault is paused:

  1. Check drawdown status in vault dashboard

  2. Evaluate options:

    • Wait for market recovery

    • Close positions to lock in losses

    • Request unpause if balance recovered

  3. Communicate with investor

If oracle fails:

  1. Cannot trade (freshness check fails)

  2. Contact admin to check oracle status

  3. Wait for oracle recovery

Investor Protections

Monitoring:

  • Real-time vault status updates

  • Email notifications for pauses (future)

  • Access to full trade audit trail

Actions:

  • Cannot directly pause/unpause (only trader can)

  • Can trigger vault closure if deadline passed

  • Can view all positions and PnL in real-time

Admin Interventions

Oracle Maintenance:

  • Restart oracle service if stopped

  • Verify blockchain connectivity

  • Check for syncing issues

Emergency Shutdown:

  • Not implemented (vaults are non-custodial)

  • Would require smart contract upgrade

Best Practices for Traders

Stay Within Limits

Know your limits before trading:

Max Drawdown: 20%
Current Drawdown: 15%
Buffer Remaining: 5%

Risk Management:

  • Keep buffer of 5-10% below limits

  • Don't trade all the way to the edge

  • Use stop-losses to prevent auto-pause

Monitor Balance Freshness

Before large trades:

  1. Check lastBalanceUpdate timestamp

  2. Ensure update within last 2-3 minutes

  3. If stale, wait for next oracle update

Best Practice: Refresh vault status every few minutes during active trading.

Communicate with Investors

Transparency builds trust:

  • Notify investor if approaching limits

  • Explain strategy if paused and recovering

  • Provide updates on position management

Recovery Strategies

If vault is paused:

Option A: Close and Accept Loss

  • Close all positions

  • Lock in realized loss

  • Close vault and distribute remaining capital

Option B: Wait for Recovery

  • Keep positions open

  • Hope market reverses

  • Risk: Could get worse (liquidation)

Option C: Partial Close

  • Close losing positions

  • Keep winners running

  • Reduce overall risk while maintaining some exposure

Monitoring Dashboard

Vault Status Panel:

  • Current balance (updated every 60s)

  • Current drawdown (% from peak)

  • Daily drawdown (% from day start)

  • Time to deal deadline

  • Last oracle update time

  • Pause status

Alerts:

  • Warning: Within 5% of limit

  • Critical: Within 2% of limit

  • Paused: Limit breached

Technical Details

Smart Contract Functions

Pause/Unpause:

function pause() external onlyOracle {
  paused = true;
  emit VaultPaused(block.timestamp);
}

function unpause() external onlyTrader {
  require(!closed, "Vault is closed");
  require(hyperliquidBalance >= pauseThreshold, "Still below limit");
  paused = false;
  emit VaultUnpaused(block.timestamp);
}

Balance Update:

function updateBalance(uint256 newBalance) external onlyOracle {
  uint256 oldBalance = hyperliquidBalance;
  hyperliquidBalance = newBalance;
  lastBalanceUpdate = block.timestamp;

  // Check max drawdown
  if (newBalance < peakBalance * (100 - maxDrawdown) / 100) {
    pause();
  }

  // Check daily drawdown
  if (newBalance < dayStartBalance * (100 - dailyDrawdown) / 100) {
    pause();
  }

  // Update peak if new high
  if (newBalance > peakBalance) {
    peakBalance = newBalance;
  }

  emit BalanceUpdated(oldBalance, newBalance);
}

Next Steps

Last updated