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

# Keeper Infrastructure

> Running keeper bots, KeeperMonitor, RebalanceHandler, and earning bounties.

Keepers are off-chain agents that monitor the Symmetry protocol and execute pending tasks in exchange for bounties. All keeper operations are permissionless — every SDK method in the rebalance flow (`updateTokenPricesTx`, `flashSwapTx`, `mintTx`, `redeemTokensTx`, `claimBountyTx`, etc.) can be called by any wallet. The `keeper` parameter is simply the signing wallet.

Developers can build their own keeper bots using the individual SDK methods directly. The SDK also ships with `KeeperMonitor` and `RebalanceHandler` as reference implementations that handle the full lifecycle automatically — use them as-is, or as a starting point for custom keeper logic.

## KeeperMonitor

`KeeperMonitor` is a reference keeper implementation included in the SDK. It continuously polls the protocol and automatically handles:

* Executing configuration intents after activation
* Cancelling expired intents
* Processing rebalance intents (price updates, flash swaps, minting, redeeming, bounty claims)

### Setup

```typescript theme={null}
import { KeeperMonitor } from "@symmetry-hq/sdk";
import { Connection, Keypair } from "@solana/web3.js";

const keypair = Keypair.fromSecretKey(/* your secret key */);
const wallet = {
  publicKey: keypair.publicKey,
  signTransaction: async (tx) => { tx.sign([keypair]); return tx; },
  signAllTransactions: async (txs) => { txs.forEach(tx => tx.sign([keypair])); return txs; },
  payer: keypair,
};

const connection = new Connection("https://api.mainnet-beta.solana.com");

const keeper = new KeeperMonitor({
  wallet,
  connection,
  network: "mainnet",
  jupiterApiKey: "<YOUR_JUPITER_API_KEY>",
  maxAllowedAccounts: 64,
  priorityFee: 50_000,
  simulateTransactions: false,
});
```

### Running

**Continuous loop:**

```typescript theme={null}
while (true) {
  await keeper.update();
  await new Promise(resolve => setTimeout(resolve, 10_000));
}
```

**Time-limited run:**

```typescript theme={null}
await keeper.run(600); // run for 10 minutes (minimum 60 seconds)
```

The `run()` method calls `update()` every \~30 seconds, stops after the specified duration, waits 45 seconds for in-flight tasks, then clears internal state.

### How It Works

Each `update()` call:

1. Fetches all vaults and syncs them to an internal map.
2. Fetches all configuration intents. For new intents, starts `monitorIntent()` in the background.
3. Fetches all rebalance intents. For new actionable ones (not `deposit_tokens` or `not_active`), starts `monitorRebalanceIntent()` in the background.
4. Removes closed intents from internal maps.

### Intent Monitoring

For each configuration intent:

1. Wait until `activation_timestamp`.
2. Try to execute via `executeVaultIntentTx` (up to 2 attempts).
3. If execution failed, wait until expiration.
4. After expiration, try to cancel via `cancelVaultIntentTx` (up to 4 attempts).

### Rebalance Intent Monitoring

For each rebalance intent:

1. Skip if `not_active` or `deposit_tokens` (waiting for user action).
2. If `update_prices`: call `updateTokenPricesTx` (up to 5 attempts).
3. During auction windows:
   * Compute swap pairs via `getSwapPairs`.
   * Fetch Jupiter quotes for profitable pairs (refreshed every 60 seconds).
   * Execute `flashSwapTx` for each profitable pair.
   * Re-check every \~8 seconds.
4. After auctions end:
   * Deposits: call `mintTx` (up to 3 attempts).
   * Withdrawals with remaining tokens: call `redeemTokensTx` (up to 3 attempts). **Note:** if the recipient's ATA doesn't exist for a token and the keeper is not the owner, that token is skipped during redemption. Ensure the withdrawer has ATAs for all expected tokens.
   * Then: call `claimBountyTx` (up to 3 attempts).

### Requirements

| Requirement     | Details                                           |
| --------------- | ------------------------------------------------- |
| SOL balance     | Keeper wallet needs SOL for transaction fees      |
| Jupiter API key | Required for flash swap quotes on mainnet         |
| RPC connection  | Reliable RPC with sufficient rate limits          |
| Uptime          | Keepers should run continuously to not miss tasks |

## RebalanceHandler

`RebalanceHandler` handles a single rebalance intent from start to finish. Use this when you want to process a specific rebalance rather than monitoring all protocol activity.

### One-Shot Run

```typescript theme={null}
import { RebalanceHandler } from "@symmetry-hq/sdk";
import { PublicKey } from "@solana/web3.js";

await RebalanceHandler.run({
  intentPubkey: new PublicKey("<REBALANCE_INTENT_PUBKEY>"),
  wallet,
  connection,
  network: "mainnet",
  jupiterApiKey: "<JUP_API_KEY>",
  maxAllowedAccounts: 64,
  priorityFee: 50_000,
  simulateTransactions: false,
});
```

This fetches the rebalance intent and its vault, creates a handler instance, runs the full lifecycle, and refreshes intent data periodically (every 15 seconds, up to 20 refreshes).

### Manual Control

```typescript theme={null}
const handler = new RebalanceHandler({
  intent: uiRebalanceIntent,
  vault: vault,
  wallet,
  connection,
  network: "mainnet",
  jupiterApiKey: "<JUP_API_KEY>",
  maxAllowedAccounts: 64,
  priorityFee: 50_000,
  simulateTransactions: false,
});
```

## Bounty System

Keepers earn bounties for completing tasks. Bounties are funded by the user or manager who creates the intent.

### How Bounties Work

1. When a deposit, withdrawal, rebalance, or configuration change is created, a bounty is deposited (typically in WSOL). This includes a **bounty bond** — a fixed amount (set in the protocol's global config as `bounty_bond_amount`) that is locked alongside the bounty.
2. The bounty has a schedule: starts at `min_bounty` and increases to `max_bounty` over time, incentivizing faster execution.
3. Each task completion (price update, flash swap, mint, redeem) is recorded on-chain with the keeper's pubkey and timestamp.
4. After all tasks are done, `claimBountyTx` distributes bounties to all participating keepers proportionally based on the tasks they completed.
5. Unused bounty and the bounty bond are returned to the depositor.
6. The on-chain account rent is also returned.

### Bounty Schedule

```typescript theme={null}
interface FormattedBountySchedule {
  min_bounty: number;           // raw amount in bounty token's smallest units
  max_bounty: number;           // raw amount in bounty token's smallest units
  min_bounty_until: number;
  max_bounty_after: number;
}
```

Between `min_bounty_until` and `max_bounty_after`, the bounty interpolates linearly.

### Bounty Computation

The total WSOL locked when creating a deposit, withdrawal, or rebalance is computed automatically by the SDK. It includes:

| Component             | Description                                                                                                                                                |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bounty bond           | Fixed lock amount from global config (`bounty_bond_amount`). Returned after completion.                                                                    |
| Task bounties         | `max_bounty_per_task` × number of tasks (auction stages, mint/redeem, claim).                                                                              |
| Price update bounties | `max_bounty_per_task / bounty_per_price_update_task_divisor` × number of tokens in the vault. More tokens = more price update instructions = higher total. |

Users can override the min/max bounty per task via `min_bounty_amount` and `max_bounty_amount` parameters in `buyVaultTx`, `sellVaultTx`, and `rebalanceVaultTx`. Higher bounties incentivize keepers to process operations faster.

The SDK computes the total automatically — callers do not need to calculate it manually.

### Adding Bounty to a Vault

Vault bounty funds automation (keeper-initiated rebalances):

```typescript theme={null}
const tx: TxPayloadBatchSequence = await sdk.addBountyTx({
  keeper: wallet.publicKey.toBase58(),
  vault: "<VAULT_PUBKEY>",
  amount: 100_000_000,  // 0.1 SOL (raw: 0.1 * 10^9 = 100_000_000 lamports)
});
```

## Jupiter Integration

The SDK includes utilities for interacting with Jupiter for flash swaps:

```typescript theme={null}
import { getJupTokenLedgerAndSwapInstructions } from "@symmetry-hq/sdk";

const result = await getJupTokenLedgerAndSwapInstructions({
  keeper: walletPublicKey,
  vaultMintIn: new PublicKey("<TOKEN_VAULT_RECEIVES>"),   // token deposited to vault
  vaultMintOut: new PublicKey("<TOKEN_VAULT_GIVES>"),     // token withdrawn from vault
  vaultAmountIn: 1_000_000,    // raw amount vault receives
  vaultAmountOut: 1_000_000,   // raw amount vault gives
  swapMode: "ioc",
  apiKey: "<JUPITER_API_KEY>",
  maxJupAccounts: 64,
});

// result contains:
// - tokenLedgerInstruction: TransactionInstruction
// - swapInstruction: TransactionInstruction
// - addressLookupTableAddresses: PublicKey[]
// - quoteResponse: { inAmount, outAmount, ... }
```

### Swap Modes

| Mode        | Description                                   |
| ----------- | --------------------------------------------- |
| `exact_in`  | Guarantee exact input amount, variable output |
| `exact_out` | Guarantee exact output amount, variable input |
| `ioc`       | Immediate-or-cancel — best effort execution   |

<Note>
  The Jupiter API call uses reversed mint directions internally. The SDK handles this — you specify `vaultMintIn` (what the vault receives) and `vaultMintOut` (what the vault gives), and the SDK swaps them for the Jupiter query.
</Note>
