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

# Add a Protocol

> Add lending protocols or DEXs to the Stylus contracts

The Stylus contracts use dynamic registries -- indexed mappings with soft-delete
removal -- so new protocols and DEXes can be added on-chain without
redeployment. This guide covers both workflows.

<Tabs>
  <Tab title="Lending Protocol">
    ## Adding a Lending Protocol to LiquidationMonitor

    The LiquidationMonitor contract maintains a registry of lending protocols
    it can scan for at-risk accounts. Each protocol entry has an address, a
    type constant, and an active flag.

    ### Supported Protocol Types

    | Type Constant              | Value | Protocol               |
    | -------------------------- | ----- | ---------------------- |
    | `LENDING_TYPE_AAVE_V3`     | `0`   | Aave V3                |
    | `LENDING_TYPE_COMPOUND_V3` | `1`   | Compound V3 (reserved) |

    <Steps>
      <Step title="Define the protocol interface">
        If the new protocol uses a different interface than Aave V3, add a
        `sol_interface!` declaration in `contracts/liquidation-monitor/src/lib.rs`:

        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        sol_interface! {
            interface ICompoundComet {
                function borrowBalanceOf(address account)
                    external view returns (uint256);
                function getAssetInfo(uint8 i)
                    external view returns (
                        uint8 offset,
                        address asset,
                        address priceFeed,
                        uint64 scale,
                        uint64 borrowCollateralFactor,
                        uint64 liquidateCollateralFactor,
                        uint64 liquidationFactor,
                        uint128 supplyCap
                    );
            }
        }
        ```
      </Step>

      <Step title="Add a type constant">
        Define a new constant for the protocol type:

        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        const LENDING_TYPE_COMPOUND_V3: u64 = 1;
        ```

        The contract dispatches health factor queries based on this type
        constant, so each protocol needs a unique value.
      </Step>

      <Step title="Add dispatch logic in the health scanner">
        In the `get_health_factor` method, add a branch for the new type:

        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        if protocol_type == U256::from(LENDING_TYPE_COMPOUND_V3) {
            let comet = ICompoundComet::new(pool_address);
            let borrow_balance = comet.borrow_balance_of(
                self.vm(), Call::new(), account
            )?;
            // Calculate health factor based on Compound V3 logic
            // ...
        }
        ```
      </Step>

      <Step title="Register the protocol on-chain">
        Call `add_lending_protocol` with the pool address and type constant:

        ```bash theme={"theme":{"light":"dracula","dark":"dracula"}}
        cast send LIQUIDATION_MONITOR_ADDRESS \
          "add_lending_protocol(address,uint64)" \
          COMPOUND_COMET_ADDRESS 1 \
          --rpc-url https://arb1.arbitrum.io/rpc \
          --private-key 0xOWNER_KEY
        ```

        This emits a `LendingProtocolAdded` event and returns the registry
        index.
      </Step>

      <Step title="Write tests">
        Add tests using the `TestVM` and `mock_static_call` patterns:

        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        #[test]
        fn test_compound_health_factor() {
            let (vm, mut monitor) = setup();
            vm.set_sender(OWNER);

            // Register Compound V3
            let idx = monitor
                .add_lending_protocol(COMPOUND_ADDR, 1)
                .unwrap();

            // Mock the borrow_balance_of call
            let calldata = /* build calldata */;
            vm.mock_static_call(
                COMPOUND_ADDR, calldata,
                Ok(U256::from(1000).to_be_bytes_vec())
            );

            // Test health factor query
            let hf = monitor.get_health_factor(ALICE).unwrap();
            assert!(hf > U256::ZERO);
        }
        ```

        <Info>
          `mock_static_call` in stylus-test 0.10.0 always returns the LAST
          registered mock's data. Register losing mocks first, then the
          winning mock last.
        </Info>
      </Step>
    </Steps>
  </Tab>

  <Tab title="DEX">
    ## Adding a DEX to RouteOptimizer

    The RouteOptimizer maintains a registry of DEX quoters it queries for
    route comparison. Each DEX has an address, a type constant, and an
    active flag. Maximum 20 DEXes per contract.

    ### Supported DEX Types

    | Type Constant     | Value | Protocol                                      |
    | ----------------- | ----- | --------------------------------------------- |
    | `DEX_TYPE_UNIV3`  | `0`   | Uniswap V3 (concentrated liquidity)           |
    | `DEX_TYPE_AMM_V2` | `1`   | AMM V2 (constant product: Camelot, SushiSwap) |

    <Steps>
      <Step title="Define the DEX interface (if needed)">
        The contract already has interfaces for Uniswap V3 Quoter and AMM V2
        routers. If your DEX uses a different quoting interface, add it:

        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        sol_interface! {
            interface ICurvePool {
                function get_dy(int128 i, int128 j, uint256 dx)
                    external view returns (uint256);
            }
        }
        ```
      </Step>

      <Step title="Add a type constant">
        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        const DEX_TYPE_CURVE: u64 = 2;
        ```
      </Step>

      <Step title="Add dispatch logic in find_best_route">
        In the `find_best_route` method, the contract iterates all active
        DEXes and queries each for a quote. Add handling for the new type:

        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        if dex_type == U256::from(DEX_TYPE_CURVE) {
            let pool = ICurvePool::new(dex_address);
            let amount_out = pool.get_dy(
                self.vm(), Call::new(),
                /* i */ 0.into(),
                /* j */ 1.into(),
                amount_in
            )?;
            // Compare with current best
        }
        ```
      </Step>

      <Step title="Register the DEX on-chain">
        ```bash theme={"theme":{"light":"dracula","dark":"dracula"}}
        cast send ROUTE_OPTIMIZER_ADDRESS \
          "add_dex(address,uint64)" \
          CURVE_POOL_ADDRESS 2 \
          --rpc-url https://arb1.arbitrum.io/rpc \
          --private-key 0xOWNER_KEY
        ```
      </Step>

      <Step title="Write tests">
        ```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
        #[test]
        fn test_curve_route() {
            let (vm, mut optimizer) = setup();
            vm.set_sender(OWNER);

            let idx = optimizer
                .add_dex(CURVE_ADDR, DEX_TYPE_CURVE)
                .unwrap();

            // Mock the get_dy call
            let calldata = /* build calldata */;
            vm.mock_static_call(
                CURVE_ADDR, calldata,
                Ok(U256::from(990_000).to_be_bytes_vec())
            );

            let (best_out, tokens, fees) = optimizer
                .find_best_route(TOKEN_A, TOKEN_B, U256::from(1_000_000))
                .unwrap();

            assert!(best_out > U256::ZERO);
        }
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Soft-Delete Removal Pattern

Both contracts use soft-delete rather than array compaction. When you remove
a protocol or DEX, the `active` flag is set to `false` but the entry remains
in the mapping. The scanning functions skip inactive entries.

```bash theme={"theme":{"light":"dracula","dark":"dracula"}}
# Remove a lending protocol by index
cast send LIQUIDATION_MONITOR_ADDRESS \
  "remove_lending_protocol(uint256)" 0 \
  --rpc-url https://arb1.arbitrum.io/rpc \
  --private-key 0xOWNER_KEY

# Remove a DEX by index
cast send ROUTE_OPTIMIZER_ADDRESS \
  "remove_dex(uint256)" 0 \
  --rpc-url https://arb1.arbitrum.io/rpc \
  --private-key 0xOWNER_KEY
```

Removing a protocol emits a `LendingProtocolRemoved` or `DexRemoved` event.
Attempting to remove an already-inactive entry reverts.

<Tip>
  Use `get_lending_protocol(index)` or `get_dex(index)` to check the current
  state of a registry entry before removal.
</Tip>
