# Pathfinding Integration

[Back to AMM: Automated Market Maker](https://docs.xrpl-commons.org/core-dev-bootcamp/module09bis)

***

## Introduction

One of XRPL's unique features is the deep integration between AMM pools and the pathfinding engine. Unlike other blockchains where AMM and order book liquidity are separate, XRPL's pathfinding engine can route payments through both CLOB offers and AMM pools, automatically selecting the best execution path.

This chapter explores how AMM offers are generated, how they compete with CLOB offers, and the algorithms that ensure optimal payment routing.

## Architecture Overview

### Dual Liquidity Model

```
┌─────────────────────────────────────────────────────────────┐
│                    Payment Request                          │
│                 (Source -> Destination)                     │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────┐
│                  Pathfinding Engine                         │
│                                                             │
│   ┌───────────────────┐       ┌───────────────────┐         │
│   │   Order Book      │       │    AMM Pool       │         │
│   │   (CLOB)          │       │   (Synthetic)     │         │
│   │                   │       │                   │         │
│   │  Offer 1: 2.00    │       │  Quality: 2.05    │         │
│   │  Offer 2: 2.01    │       │  (calculated)     │         │
│   │  Offer 3: 2.02    │       │                   │         │
│   └─────────┬─────────┘       └─────────┬─────────┘         │
│             │                           │                   │
│             └───────────┬───────────────┘                   │
│                         │                                   │
│                         ▼                                   │
│              ┌─────────────────────┐                        │
│              │  Best Execution     │                        │
│              │  Path Selection     │                        │
│              └─────────────────────┘                        │
└─────────────────────────────────────────────────────────────┘
```

### Key Components

| Component    | Location                                | Purpose                        |
| ------------ | --------------------------------------- | ------------------------------ |
| AMMLiquidity | `src/xrpld/app/paths/AMMLiquidity.cpp`  | Generate synthetic offers      |
| AMMOffer     | `src/xrpld/app/paths/AMMOffer.cpp`      | Represent AMM offers           |
| AMMContext   | `src/xrpld/app/paths/AMMContext.h`      | Track AMM state during payment |
| BookStep     | `src/xrpld/app/paths/impl/BookStep.cpp` | Integrate AMM with order book  |

## AMMContext: State Management

The `AMMContext` class tracks AMM usage during payment execution.

**Location:** `src/xrpld/app/paths/AMMContext.h`

```cpp
class AMMContext {
    AccountID account_;           // Transaction sender
    bool multiPath_;              // Using multiple paths?
    bool ammUsed_;               // AMM consumed this iteration?
    std::uint16_t ammIters_;     // AMM iteration counter

    constexpr static std::uint8_t MaxIterations = 30;

public:
    // Mark AMM offer as consumed
    void setAMMUsed() { ammUsed_ = true; }

    // Called after each iteration
    void update() {
        if (ammUsed_)
            ++ammIters_;
        ammUsed_ = false;  // Reset for next iteration
    }

    // Check iteration limit
    bool maxItersReached() const {
        return ammIters_ >= MaxIterations;
    }

    // Reset for new iteration
    void clear() { ammUsed_ = false; }
};
```

### Iteration Limit

The 30-iteration limit prevents:

* Infinite loops in complex path finding
* Excessive computation for large payments
* Potential DoS through pathfinding abuse

## AMMLiquidity: Offer Generation

The `AMMLiquidity` class generates synthetic offers from AMM pools.

**Location:** `src/xrpld/app/paths/AMMLiquidity.cpp`

```cpp
template <typename TIn, typename TOut>
class AMMLiquidity {
    AccountID ammAccountID_;
    std::uint32_t tradingFee_;
    Issue issueIn_;
    Issue issueOut_;
    TAmounts<TIn, TOut> initialBalances_;

public:
    // Get AMM offer for payment path
    std::optional<AMMOffer<TIn, TOut>>
    getOffer(ReadView const& view, AMMContext& ctx) const;

    // Fetch current pool balances
    TAmounts<TIn, TOut>
    fetchBalances(ReadView const& view) const;

    // Generate offer matching CLOB quality
    std::optional<TAmounts<TIn, TOut>>
    generateOfferForQuality(Quality const& quality) const;

    // Fibonacci sequence for multi-path
    std::optional<TAmounts<TIn, TOut>>
    generateFibSeqOffer(std::uint16_t iteration) const;
};
```

### Offer Generation Strategies

#### Single Path Mode

When processing a single payment path, AMM offers are sized to match CLOB quality:

```cpp
std::optional<AMMOffer<TIn, TOut>>
AMMLiquidity::getOffer(ReadView const& view, AMMContext& ctx) const
{
    // Get best CLOB offer quality
    auto const clobQuality = getBestCLOBQuality(view);

    // Generate AMM offer matching this quality
    auto const amounts = changeSpotPriceQuality(
        balances_,
        clobQuality,
        tradingFee_
    );

    if (!amounts)
        return std::nullopt;

    return AMMOffer<TIn, TOut>(*this, *amounts, balances_, quality);
}
```

#### Multi-Path Mode

For payments using multiple paths, Fibonacci sequence sizing prevents over-concentration:

```cpp
std::optional<TAmounts<TIn, TOut>>
AMMLiquidity::generateFibSeqOffer(std::uint16_t iteration) const
{
    // Fibonacci fractions: 5/20000, 8/20000, 13/20000, 21/20000, ...
    static constexpr std::array<std::uint16_t, 10> fib = {
        5, 8, 13, 21, 34, 55, 89, 144, 233, 377
    };

    auto const fraction = Number(fib[iteration]) / 20000;
    auto const offerIn = balances_.in * fraction;
    auto const offerOut = swapAssetIn(balances_, offerIn, tradingFee_);

    return TAmounts{offerIn, offerOut};
}
```

## AMMOffer: Synthetic Offer Representation

The `AMMOffer` class represents an AMM-backed offer in the pathfinding system. Unlike CLOB offers which are stored on the ledger, **AMMOffers are created dynamically** during payment execution.

**Location:** `src/xrpld/app/paths/AMMOffer.cpp`

### When Are AMMOffers Created?

AMMOffers are **not ledger objects** - they are synthetic, ephemeral offers generated on-the-fly:

```
┌─────────────────────────────────────────────────────────────────┐
│                    AMMOffer Lifecycle                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Payment Request                                             │
│         │                                                       │
│         ▼                                                       │
│  2. BookStep processes order book for currency pair             │
│         │                                                       │
│         ▼                                                       │
│  3. AMMLiquidity::getOffer() called                             │
│     ┌─────────────────────────────────────────────────────┐     │
│     │ - Reads current pool balances from ltAMM            │     │
│     │ - Looks at best CLOB offer quality                  │     │
│     │ - Calculates AMM offer to match/beat that quality   │     │
│     │ - Creates NEW AMMOffer object (in memory only!)     │     │
│     └─────────────────────────────────────────────────────┘     │
│         │                                                       │
│         ▼                                                       │
│  4. AMMOffer competes with CLOB offers on quality               │
│         │                                                       │
│         ▼                                                       │
│  5. If AMM wins: AMMOffer::consume() applies the swap           │
│     - Pool balances updated in sandbox                          │
│     - AMMOffer marked as consumed                               │
│         │                                                       │
│         ▼                                                       │
│  6. New AMMOffer generated for remaining payment (if any)       │
│     - Fresh calculation with updated pool balances              │
│     - Cycle repeats until payment complete or max iterations    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

### Key Insight: AMMOffers Are Ephemeral

| CLOB Offer                      | AMMOffer                             |
| ------------------------------- | ------------------------------------ |
| Stored on ledger                | Created in memory                    |
| Persists until filled/cancelled | Exists only during payment execution |
| Has explicit size               | Size calculated dynamically          |
| Fixed quality                   | Quality adapts to pool state         |
| Created by OfferCreate tx       | Created by AMMLiquidity::getOffer()  |

### How AMMOffers Are Sized

The size of an AMMOffer depends on the context:

**Single-Path Mode:**

```cpp
// Match the best CLOB quality
auto amounts = changeSpotPriceQuality(pool, clobQuality, fee);
// AMMOffer is sized so that AFTER the trade,
// the pool's new spot price equals the CLOB quality
```

**Multi-Path Mode:**

```cpp
// Use Fibonacci sequence to avoid over-consumption
// Iteration 0: 5/20000 of pool (0.025%)
// Iteration 1: 8/20000 of pool (0.04%)
// Iteration 2: 13/20000 of pool (0.065%)
// ... growing offers ensure gradual consumption
```

### Structure

```cpp
template <typename TIn, typename TOut>
class AMMOffer {
    AMMLiquidity<TIn, TOut> const& ammLiquidity_;
    TAmounts<TIn, TOut> amounts_;      // Offer amounts
    TAmounts<TIn, TOut> balances_;     // Current pool balances
    Quality quality_;                   // Spot price quality
    bool consumed_;                     // Has been consumed?

public:
    // Get offer quality (exchange rate)
    Quality quality() const { return quality_; }

    // Limit output to specific amount
    void limitOut(TOut const& limit);

    // Limit input to specific amount
    void limitIn(TIn const& limit);

    // Apply offer to AMM pool
    void consume();

    // Verify pool invariant after trade
    bool checkInvariant() const;
};
```

### Quality Calculation

```cpp
Quality AMMOffer::quality() const
{
    // Quality = out / in (how much you get per unit spent)
    return Quality(amounts_.out / amounts_.in);
}
```

### Limiting Offers

When the payment doesn't need the full offer:

```cpp
void AMMOffer::limitOut(TOut const& limit)
{
    if (limit < amounts_.out)
    {
        // Recalculate input for limited output
        amounts_.in = swapAssetOut(balances_, limit, tradingFee_);
        amounts_.out = limit;
    }
}

void AMMOffer::limitIn(TIn const& limit)
{
    if (limit < amounts_.in)
    {
        // Recalculate output for limited input
        amounts_.out = swapAssetIn(balances_, limit, tradingFee_);
        amounts_.in = limit;
    }
}
```

### Consuming Offers

```cpp
void AMMOffer::consume()
{
    // Mark as consumed
    consumed_ = true;

    // Update balances (for next iteration)
    balances_.in += amounts_.in;
    balances_.out -= amounts_.out;

    // Verify invariant
    assert(checkInvariant());
}
```

## Integration with BookStep

The `BookStep` class handles order book traversal, including AMM integration.

**Location:** `src/xrpld/app/paths/impl/BookStep.cpp`

### Processing Order

```cpp
std::pair<TAmounts, bool>
BookStep::compute(
    PaymentSandbox& view,
    AMMContext& ammContext,
    ...)
{
    // 1. Get CLOB offers
    auto clobOffers = getOrderBookOffers(view);

    // 2. Get AMM offer (if available)
    auto ammOffer = ammLiquidity_.getOffer(view, ammContext);

    // 3. Interleave based on quality
    while (hasOffers())
    {
        // Compare qualities
        auto const clobQuality = clobOffers.front().quality();
        auto const ammQuality = ammOffer ? ammOffer->quality() : Quality{0};

        if (ammQuality >= clobQuality && ammOffer)
        {
            // Use AMM offer (better or equal quality)
            consumeAMMOffer(*ammOffer, ammContext);

            // Generate new AMM offer for remaining
            ammOffer = ammLiquidity_.getOffer(view, ammContext);
        }
        else
        {
            // Use CLOB offer
            consumeCLOBOffer(clobOffers.front());
            clobOffers.pop_front();
        }
    }

    return {totalConsumed, success};
}
```

### Quality Competition

AMM and CLOB offers compete purely on quality (exchange rate):

```
If AMM quality >= CLOB quality:
    Use AMM offer
Else:
    Use CLOB offer
```

This ensures best execution regardless of liquidity source.

## Multi-Path Payment Example

### Scenario

Payment: 10,000 XRP -> USD Available liquidity:

* CLOB: 5,000 XRP @ 2.00 USD/XRP
* CLOB: 3,000 XRP @ 1.98 USD/XRP
* AMM Pool: 100,000 XRP / 200,000 USD (spot: 2.00)

### Execution

```
Iteration 1:
  - CLOB offer: 5,000 XRP @ 2.00
  - AMM offer: Generated to match 2.00 quality
  - Result: Split between both at equal quality

Iteration 2:
  - CLOB offer: 3,000 XRP @ 1.98
  - AMM offer: New quality after iteration 1
  - Result: CLOB wins (better quality)

Iteration 3:
  - Remaining: ~2,000 XRP
  - AMM fills remainder at current pool quality

Total: 10,000 XRP -> ~19,800 USD
(Blend of CLOB and AMM liquidity)
```

## Flow Diagram

```
┌─────────────────────────────────────────────────────────────┐
│                    Payment Flow                             │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼
              ┌─────────────────────┐
              │  BookStep::compute  │
              └──────────┬──────────┘
                         │
           ┌─────────────┴─────────────┐
           │                           │
           ▼                           ▼
┌────────────────────┐     ┌────────────────────┐
│  Get CLOB Offers   │     │  Get AMM Offer     │
│  from OrderBook    │     │  from AMMLiquidity │
└─────────┬──────────┘     └─────────┬──────────┘
          │                          │
          └────────────┬─────────────┘
                       │
                       ▼
              ┌─────────────────────┐
              │  Compare Qualities  │
              │  AMM vs CLOB        │
              └──────────┬──────────┘
                         │
         ┌───────────────┴───────────────┐
         │                               │
         ▼                               ▼
┌─────────────────┐            ┌─────────────────┐
│  AMM Better?    │            │  CLOB Better?   │
│  Consume AMM    │            │  Consume CLOB   │
│  Update Context │            │  Next Offer     │
└────────┬────────┘            └────────┬────────┘
         │                              │
         └──────────────┬───────────────┘
                        │
                        ▼
               ┌─────────────────┐
               │  More to Pay?   │
               │  Continue Loop  │
               └─────────────────┘
```

## Performance Considerations

### Iteration Limits

```cpp
// Maximum AMM iterations per payment
constexpr std::uint8_t MaxIterations = 30;
```

Why 30?

* Balances payment quality with computation cost
* Prevents pathfinding abuse
* Sufficient for most payment sizes

### Quality Caching

```cpp
// Cache quality calculations to avoid recomputation
Quality cachedQuality_;
bool qualityValid_ = false;

Quality getQuality() {
    if (!qualityValid_) {
        cachedQuality_ = computeQuality();
        qualityValid_ = true;
    }
    return cachedQuality_;
}
```

### Balance Snapshots

```cpp
// Snapshot balances at start to avoid repeated ledger reads
TAmounts<TIn, TOut> initialBalances_;

AMMLiquidity(...) {
    initialBalances_ = fetchBalances(view);
}
```

## Edge Cases

### Empty AMM Pool

```cpp
if (balances_.in == 0 || balances_.out == 0)
    return std::nullopt;  // No offer available
```

### Iteration Limit Reached

```cpp
if (ammContext.maxItersReached())
    return std::nullopt;  // Stop using AMM
```

### Quality Worse Than CLOB

```cpp
if (ammQuality < minAcceptableQuality)
    return std::nullopt;  // Skip AMM for this path
```

## Debugging Tips

### Tracing AMM Offers

Enable detailed logging:

```cpp
JLOG(j_.trace()) << "AMM offer: "
    << "in=" << amounts_.in
    << " out=" << amounts_.out
    << " quality=" << quality_;
```

### Verifying Integration

Check that AMM is being considered:

```bash
# In rippled logs
grep "AMM offer" debug.log
```

### Testing Quality Competition

Create test scenarios with known CLOB and AMM qualities to verify correct selection.

## Summary

The AMM pathfinding integration enables:

1. **Best Execution**: Automatic selection between AMM and CLOB
2. **Seamless Liquidity**: Users don't need to know liquidity source
3. **Fair Competition**: Quality-based selection ensures best rates
4. **Bounded Computation**: Iteration limits prevent abuse
5. **Multi-Path Support**: Fibonacci sizing for complex payments

This integration is unique to XRPL and provides significant advantages over systems where AMM and order book liquidity are separate.

## References to Source Code

* `src/xrpld/app/paths/AMMLiquidity.cpp` - Offer generation
* `src/xrpld/app/paths/AMMOffer.cpp` - Offer representation
* `src/xrpld/app/paths/AMMContext.h` - State management
* `src/xrpld/app/paths/impl/BookStep.cpp` - Integration point
* `src/xrpld/app/misc/AMMHelpers.h` - Quality calculations

## Cross-References

* [AMM Architecture](https://docs.xrpl-commons.org/core-dev-bootcamp/module09bis/amm-architecture) - Pool structure
* [AMM Logic](https://docs.xrpl-commons.org/core-dev-bootcamp/module09bis/amm-logic) - Quality and swap formulas
* [Module 02: Transaction Lifecycle](https://docs.xrpl-commons.org/core-dev-bootcamp/module02/transaction-lifecycle-complete-transaction-journey) - Payment flow
