> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Hyperliquid agent wallets

> Use Dynamic's Node SDK to create MPC master wallets and manage agent wallets (API wallets) for automated, programmatic trading on Hyperliquid

[Agent wallets](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets#api-wallets) (also called API wallets) are permissioned signers that hold no funds but can execute Hyperliquid actions on behalf of a master account. With Dynamic's Node SDK you manage the master wallet server-side via MPC, while agent wallets are lightweight ephemeral keypairs generated with viem.

This pattern lets you:

* Keep funds locked in a secure MPC master account
* Rotate agent keys without disrupting active trading
* Give each bot or strategy its own isolated nonce space
* Preserve consolidated fee tiers and account-level PnL under one master account

<Note>
  If you are building a browser or React Native app where a human user controls the master wallet, see the [Hyperliquid SDK integration](/recipes/integrations/hyperliquid) recipe instead. This recipe is for **server-side / agentic** use cases where the master wallet is managed programmatically.
</Note>

## How it works

```
┌──────────────────────────────────────────┐
│  Dynamic Node SDK (MPC)                  │
│  Master wallet — holds funds, signs      │
│  approveAgent once per agent             │
└───────────────────┬──────────────────────┘
                    │ approveAgent()
                    ▼
┌──────────────────────────────────────────┐
│  viem-generated keypair                  │
│  Agent wallet — holds no funds,          │
│  signs all trade orders                  │
└──────────────────────────────────────────┘
```

Dynamic's Node SDK creates one MPC wallet per chain — ideal for the master account that secures your funds. Agent wallets are generated with `generatePrivateKey` from viem and stored in your secrets manager.

<Warning>
  The master wallet must be funded on Hyperliquid before `approveAgent` can be called. Fund it via the [Hyperliquid app](https://app.hyperliquid.xyz) (mainnet) or [testnet app](https://app.hyperliquid-testnet.xyz) first. The testnet faucet requires a prior mainnet deposit — the simplest path is to use a pre-funded account or test on mainnet with a small amount.
</Warning>

## Installation

<CodeGroup>
  ```bash npm theme={"system"}
  npm install @dynamic-labs-wallet/node-evm @nktkas/hyperliquid viem
  ```

  ```bash yarn theme={"system"}
  yarn add @dynamic-labs-wallet/node-evm @nktkas/hyperliquid viem
  ```

  ```bash pnpm theme={"system"}
  pnpm add @dynamic-labs-wallet/node-evm @nktkas/hyperliquid viem
  ```

  ```bash bun theme={"system"}
  bun add @dynamic-labs-wallet/node-evm @nktkas/hyperliquid viem
  ```
</CodeGroup>

## Environment setup

```bash theme={"system"}
# .env
DYNAMIC_ENVIRONMENT_ID=your-environment-id
DYNAMIC_AUTH_TOKEN=your-auth-token
IS_TESTNET=true
```

## Step 1: Initialize the Dynamic client

```typescript theme={"system"}
import { DynamicEvmWalletClient } from '@dynamic-labs-wallet/node-evm';

export const getEvmClient = async () => {
  const client = new DynamicEvmWalletClient({
    environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
  });
  await client.authenticateApiToken(process.env.DYNAMIC_AUTH_TOKEN!);
  return client;
};
```

## Step 2: Create or retrieve the master wallet

<Note>
  For this pattern it makes sense to use a single master wallet per chain — one account that holds funds and signs agent approvals. Create it once, persist the address, and reuse it on every subsequent run.
</Note>

```typescript theme={"system"}
import { ThresholdSignatureScheme } from '@dynamic-labs-wallet/node';
import { privateKeyToAccount } from 'viem/accounts';
import { getEvmClient } from './client';

// Persist this after first creation (database, env var, etc.)
const MASTER_WALLET_ADDRESS = process.env.MASTER_WALLET_ADDRESS ?? '';

export const getOrCreateMasterWallet = async () => {
  const evmClient = await getEvmClient();

  if (MASTER_WALLET_ADDRESS) {
    return evmClient.getWallet({ accountAddress: MASTER_WALLET_ADDRESS });
  }

  const wallet = await evmClient.createWalletAccount({
    thresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
    backUpToClientShareService: true,
  });
  // Store wallet.accountAddress and wallet.walletId in your database
  return wallet;
};

// Export as a viem account for signing Hyperliquid actions
export const getMasterViemAccount = async (masterAddress: string) => {
  const evmClient = await getEvmClient();
  const { derivedPrivateKey } = await evmClient.exportPrivateKey({
    accountAddress: masterAddress,
  });
  // derivedPrivateKey may omit the 0x prefix — normalise it
  const key = derivedPrivateKey?.startsWith('0x')
    ? derivedPrivateKey
    : (`0x${derivedPrivateKey ?? ''}` as `0x${string}`);
  return privateKeyToAccount(key as `0x${string}`);
};
```

<Warning>
  Never log or persist the derived private key. Use `getMasterViemAccount` to get an in-memory signer only, and discard the key immediately after use.
</Warning>

## Step 3: Create or load the agent wallet

```typescript theme={"system"}
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';

export const createAgentWallet = () => {
  const privateKey = generatePrivateKey();
  const account = privateKeyToAccount(privateKey);
  // Store privateKey in your secrets manager and account.address in your database
  return { privateKey, account };
};
```

Agent keys should be stored in a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) and looked up by address or bot ID at runtime. The key itself never needs to touch your application database.

## Step 4: Approve the agent on Hyperliquid

The `approveAgent` transaction is signed by the master wallet and recorded on-chain. It only needs to be called once per agent address — checking `extraAgents` first keeps the function idempotent.

```typescript theme={"system"}
import * as hl from '@nktkas/hyperliquid';
import { getMasterViemAccount } from './masterWallet';

export const approveAgent = async ({
  masterAddress,
  agentAddress,
  agentName,
  isTestnet = false,
}: {
  masterAddress: string;
  agentAddress: string;
  agentName: string;
  isTestnet?: boolean;
}) => {
  const infoClient = new hl.InfoClient({
    transport: new hl.HttpTransport({ isTestnet }),
  });

  // Idempotency check — skip if already approved
  const existingAgents = await infoClient.extraAgents({
    user: masterAddress as `0x${string}`,
  });
  const alreadyApproved = existingAgents.some(
    (a) => a.address.toLowerCase() === agentAddress.toLowerCase(),
  );
  if (alreadyApproved) {
    console.log(`Agent ${agentAddress} is already approved, skipping.`);
    return;
  }

  const masterAccount = await getMasterViemAccount(masterAddress);
  const masterHlClient = new hl.ExchangeClient({
    transport: new hl.HttpTransport({ isTestnet }),
    wallet: masterAccount,
  });
  await masterHlClient.approveAgent({
    agentAddress: agentAddress as `0x${string}`,
    agentName,
  });
};
```

### Setting an expiry on the agent

Encode an expiry timestamp directly in the agent name using Hyperliquid's `valid_until` convention. Hyperliquid enforces this server-side.

```typescript theme={"system"}
export const approveAgentWithExpiry = async ({
  masterAddress,
  agentAddress,
  agentName,
  ttlMs = 24 * 60 * 60 * 1000, // 24 hours
  isTestnet = false,
}: {
  masterAddress: string;
  agentAddress: string;
  agentName: string;
  ttlMs?: number;
  isTestnet?: boolean;
}) => {
  const masterAccount = await getMasterViemAccount(masterAddress);
  const masterHlClient = new hl.ExchangeClient({
    transport: new hl.HttpTransport({ isTestnet }),
    wallet: masterAccount,
  });
  const expirationMs = Date.now() + ttlMs;
  await masterHlClient.approveAgent({
    agentAddress: agentAddress as `0x${string}`,
    agentName: `${agentName} valid_until ${expirationMs}`,
  });
};
```

## Step 5: Trade with the agent wallet

Once approved, the agent wallet signs all orders directly — the master wallet is not involved in individual trades.

```typescript theme={"system"}
import * as hl from '@nktkas/hyperliquid';
import { privateKeyToAccount } from 'viem/accounts';

export const placeLimitOrder = async ({
  agentPrivateKey,
  asset,
  isBuy,
  price,
  size,
  isTestnet = false,
}: {
  agentPrivateKey: string;
  asset: number;    // Hyperliquid universe index (e.g. 0 for BTC-USD perp)
  isBuy: boolean;
  price: string;
  size: string;
  isTestnet?: boolean;
}) => {
  const agentAccount = privateKeyToAccount(agentPrivateKey as `0x${string}`);
  const agentClient = new hl.ExchangeClient({
    transport: new hl.HttpTransport({ isTestnet }),
    wallet: agentAccount,
  });
  return agentClient.order({
    orders: [
      {
        a: asset,
        b: isBuy,
        p: price,
        s: size,
        r: false,
        t: { limit: { tif: 'Gtc' } },
      },
    ],
    grouping: 'na',
  });
};
```

## Listing active agents

```typescript theme={"system"}
import * as hl from '@nktkas/hyperliquid';

export const listActiveAgents = async ({
  masterAddress,
  isTestnet = false,
}: {
  masterAddress: string;
  isTestnet?: boolean;
}) => {
  const infoClient = new hl.InfoClient({
    transport: new hl.HttpTransport({ isTestnet }),
  });
  return infoClient.extraAgents({ user: masterAddress as `0x${string}` });
};
```

## Putting it all together

```typescript theme={"system"}
import { getOrCreateMasterWallet } from './masterWallet';
import { createAgentWallet } from './agentWallet';
import { approveAgent } from './approveAgent';
import { listActiveAgents, placeLimitOrder } from './trade';

const IS_TESTNET = process.env.IS_TESTNET === 'true';

// 1. Get or create the MPC master wallet
const master = await getOrCreateMasterWallet();
const masterAddress = master.accountAddress;

// 2. Create a new agent keypair (store privateKey in secrets manager)
const agent = createAgentWallet();

// 3. Approve the agent on Hyperliquid (idempotent)
await approveAgent({
  masterAddress,
  agentAddress: agent.account.address,
  agentName: 'MyTradingBot',
  isTestnet: IS_TESTNET,
});

// 4. List active agents
const agents = await listActiveAgents({ masterAddress, isTestnet: IS_TESTNET });
console.log('Active agents:', agents);

// 5. Place an order through the agent
await placeLimitOrder({
  agentPrivateKey: agent.privateKey,
  asset: 0,          // BTC-USD perp
  isBuy: true,
  price: '95000',
  size: '0.001',
  isTestnet: IS_TESTNET,
});
```

## Key concepts

| Concept                               | Detail                                                                                            |
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
| **One master wallet per environment** | Dynamic's MPC SDK enforces one wallet per chain. Create once, persist the address, reuse forever. |
| **Agent wallets don't hold funds**    | All balances stay in the master account. Agents are approved signers only.                        |
| **Idempotent approval**               | Check `extraAgents` before calling `approveAgent` to avoid redundant on-chain transactions.       |
| **Agent expiry**                      | Encode expiry in the agent name as `valid_until <unixMs>`. Hyperliquid enforces this server-side. |
| **Key rotation**                      | Generate a new agent keypair, approve it, and stop using the old one. No funds move.              |
| **Nonce isolation**                   | Each agent has its own nonce sequence — multiple bots can run concurrently without conflicts.     |

## Related

* [Hyperliquid SDK integration](/recipes/integrations/hyperliquid) — browser and React Native integration with user-controlled wallets
* [Agents Overview](/overview/agents/overview) — wallet patterns for AI agents (server wallets and delegated access)
* [Hyperliquid API documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets#api-wallets)
* [Hyperliquid SDK repository](https://github.com/nktkas/hyperliquid)
