# Priority Fees — Transaction Landing Optimization

## How Priority Fees Work

Solana transactions pay a base fee (5,000 lamports) plus an optional **priority fee** measured in **microLamports per compute unit**. The total priority fee you pay is:

```
total priority fee = compute unit price (microLamports) x compute unit limit
```

This means two things matter:
1. The **compute unit price** (how much per CU) — set via `ComputeBudgetProgram.setComputeUnitPrice`
2. The **compute unit limit** (how many CUs allocated) — set via `ComputeBudgetProgram.setComputeUnitLimit`

Transactions that request CUs closer to the actual CUs consumed will receive higher priority. A tighter CU limit also means lower total cost for the same CU price. NEVER leave the default 200,000 CU limit — simulate first.

## Getting Fee Estimates

NEVER hardcode priority fees. ALWAYS get real-time estimates from the Helius Priority Fee API.

**Preferred: Use the `getPriorityFeeEstimate` MCP tool.** It wraps the API call for you.

If calling the API directly (e.g., from generated application code), there are two approaches:

### By Account Keys (simplest)

Pass the program/account addresses your transaction interacts with:

```typescript
const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${API_KEY}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'getPriorityFeeEstimate',
    params: [{
      accountKeys: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4'],
      options: { priorityLevel: 'High' }
    }]
  })
});

const { result } = await response.json();
// result.priorityFeeEstimate = microLamports per CU
```

### By Transaction (most accurate)

Pass the serialized transaction for program-specific analysis:

```typescript
const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${API_KEY}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'getPriorityFeeEstimate',
    params: [{
      transaction: base64EncodedTransaction,
      options: {
        transactionEncoding: 'Base64',
        recommended: true,
      }
    }]
  })
});

const { result } = await response.json();
const priorityFee = result.priorityFeeEstimate;
```

### Getting All Levels At Once

Set `includeAllPriorityFeeLevels: true` to see the full spectrum:

```typescript
params: [{
  accountKeys: ['YOUR_PROGRAM_ID'],
  options: { includeAllPriorityFeeLevels: true }
}]
```

Returns:

```json
{
  "priorityFeeEstimate": 120000,
  "priorityFeeLevels": {
    "min": 0,
    "low": 10000,
    "medium": 120000,
    "high": 500000,
    "veryHigh": 1000000,
    "unsafeMax": 5000000
  }
}
```

### Options Reference

| Option | Type | Description |
|---|---|---|
| `priorityLevel` | string | `Min`, `Low`, `Medium`, `High`, `VeryHigh`, `UnsafeMax` |
| `includeAllPriorityFeeLevels` | boolean | Return all 6 levels |
| `transactionEncoding` | string | `Base58` or `Base64` (when passing transaction) |
| `lookbackSlots` | number | Slots to analyze (1-150, default varies) |
| `includeVote` | boolean | Include vote transactions in calculation |
| `recommended` | boolean | Return recommended optimal fee |
| `evaluateEmptySlotAsZero` | boolean | Count empty slots as zero-fee in calculation |

## Choosing the Right Priority Level

| Use Case | Level | Why |
|---|---|---|
| Standard transfers | `recommended: true` | Good default, next slot usually |
| DEX swaps, NFT purchases | `High` | Time-sensitive, next slot very likely |
| Arbitrage, liquidations, competitive mints | `VeryHigh` | Critical timing, next slot almost guaranteed |
| Extreme urgency, willing to overpay | `UnsafeMax` | May pay 10-100x normal fees, use sparingly |

**Default recommendation: `High` for swaps, trading, and most operations**

For production trading systems, add a buffer on top of the estimate:

```typescript
const priorityFee = Math.ceil(result.priorityFeeEstimate * 1.2); // 20% buffer
```

## Adding Fees to Transactions

### @solana/web3.js

```typescript
import { ComputeBudgetProgram } from '@solana/web3.js';

// 1. Get the estimate (via MCP tool or API call)
const feeEstimate = result.priorityFeeEstimate; // microLamports per CU

// 2. Create compute budget instructions
const computeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
  units: computeUnits, // from simulation, NOT default 200k
});

const computeUnitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: feeEstimate,
});

// 3. PREPEND to transaction — these MUST be the first two instructions
const allInstructions = [
  computeUnitLimitIx,   // first
  computeUnitPriceIx,   // second
  ...yourInstructions,   // your app logic
];
```

### @solana/kit

```typescript
import {
  getSetComputeUnitLimitInstruction,
  getSetComputeUnitPriceInstruction,
} from "@solana-program/compute-budget";

const tx = pipe(
  createTransactionMessage({ version: 0 }),
  (m) => setTransactionMessageFeePayerSigner(signer, m),
  (m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
  // Compute budget instructions first
  (m) => appendTransactionMessageInstruction(
    getSetComputeUnitLimitInstruction({ units: computeUnits }), m
  ),
  (m) => appendTransactionMessageInstruction(
    getSetComputeUnitPriceInstruction({ microLamports: feeEstimate }), m
  ),
  // Then your instructions
  (m) => appendTransactionMessageInstruction(yourInstruction, m),
);
```

### Helius SDK

```typescript
const feeEstimate = await helius.getPriorityFeeEstimate({
  accountKeys: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4'],
  options: { priorityLevel: 'High', includeAllPriorityFeeLevels: true },
});
```

```rust
// Rust
let fee_estimate = helius.rpc().get_priority_fee_estimate(GetPriorityFeeEstimateRequest {
    account_keys: Some(vec!["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4".to_string()]),
    options: Some(GetPriorityFeeEstimateOptions {
        priority_level: Some(PriorityLevel::High),
        ..Default::default()
    }),
    ..Default::default()
}).await?;
```

## Compute Unit Estimation

Do NOT use the default 200,000 CU limit. Simulate first to get actual usage, then add a margin:

```typescript
// 1. Build a test transaction with max CU for simulation
const testInstructions = [
  ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }),
  ...yourInstructions,
];

const testTx = new VersionedTransaction(
  new TransactionMessage({
    instructions: testInstructions,
    payerKey: keypair.publicKey,
    recentBlockhash: blockhash,
  }).compileToV0Message()
);
testTx.sign([keypair]);

// 2. Simulate
const simulation = await connection.simulateTransaction(testTx, {
  replaceRecentBlockhash: true,
  sigVerify: false,
});

// 3. Set limit to actual usage + 10% margin (minimum 1000 CUs)
const units = simulation.value.unitsConsumed;
const computeUnits = units < 1000 ? 1000 : Math.ceil(units * 1.1);
```

**Why this matters**: A transaction requesting 200,000 CUs at 100,000 microLamports/CU costs 20,000,000 microLamports. The same transaction at 50,000 CUs costs only 5,000,000 microLamports — 4x cheaper for better priority.

## Refresh Frequency

- Normal applications: refresh every 10-20 seconds
- Trading/swaps: refresh per transaction
- HFT/MEV: refresh every slot

## Common Mistakes

- Hardcoding priority fees instead of fetching real-time estimates
- Leaving the default 200,000 CU limit (wastes money, lowers effective priority)
- Using the same fee for all transactions instead of program-specific estimates
- Not passing `accountKeys` for the programs being interacted with (generic estimates are less accurate)
- Using `UnsafeMax` as a default (can cost 10-100x normal fees)
- Forgetting to add a buffer for production trading (network conditions can shift between estimate and submission)
