# Unified Indexer Service

The Unified Indexer Service (UIS) is the blockchain indexing architecture used by Ripple Custody for ledger-specific indexing, event processing, and ledger APIs. UIS uses a decomposed, chain-specific adapter architecture with isolated ledger and network components.

For a complete and up-to-date list of which ledgers are supported, please refer to the [Supported blockchain ledgers](/products/custody/reference/supported-ledgers) page.

UIS is available for SaaS deployments and for on-premises deployments from v1.26. If you are migrating an on-premises deployment from legacy indexers to UIS, see the [UIS on-premises migration guide](/products/custody/operations-and-maintenance/upgrading/unified-indexer-service-migration).

## Architecture

The following diagram shows how UIS components fetch blockchain data, normalize it, and expose indexed events to internal consumers such as Ledger Accounting.


```mermaid
flowchart LR
    subgraph OnPrem["On-prem"]
        direction LR
        LA["Ledger Accounting"]
        APIGW["API GW"]
        EventsAPI["Events API"]
        EventsDB[("Events DB")]

        subgraph Unified["Unified Indexers"]
            direction LR
            Processor["Processor"]
            IndexerDB[("Indexer DB")]
            GenericIndexer["Generic Indexer"]
        end

        subgraph LedgerServices["Ledger services"]
            direction TB
            LedgerAPI["Ledger API<br/>(XRPL API)"]
            Adapter["Ledger Adapter<br/>(XRPL Adapter)<br/>xrpl-mainnet<br/>xrpl-testnet"]
        end

        XRPL["XRPL"]
    end

    LA --> APIGW
    APIGW --> EventsAPI
    EventsAPI --> EventsDB
    Processor --> IndexerDB
    GenericIndexer --> IndexerDB
    Processor --> EventsDB
    GenericIndexer --> Adapter
    Adapter --> XRPL
    APIGW --> LedgerAPI
    APIGW --> Adapter
    LedgerAPI --> XRPL

    EventsDB ~~~ Processor
    IndexerDB ~~~ GenericIndexer
    GenericIndexer ~~~ LedgerAPI
```

**Data flow:**

1. Ledger Adapters connect to blockchain nodes and fetch blockchain data. For EVM chains with node authentication configured, the Ledger Adapter routes requests through the Node Gateway sidecar, which injects the required credentials before forwarding to the node.
2. The Generic Indexer orchestrates data fetching through adapters. Each ledger and network combination has its own stateful Generic Indexer instance.
3. After data is fetched, the Generic Indexer indexes the data and stores it in the outbox table.
4. The Processor reads the outbox table and generates standardized events.
5. The Processor writes events to the database.
6. The Events API reads from the database and provides indexed data to consumers.
7. Ledger Accounting pulls events through the Events API.
8. Ledger APIs provide stateless access to live blockchain data from nodes, not indexed data.


## Components

The UIS consists of several components that work together:

| Component | Description |
|  --- | --- |
| **Generic Indexer** | The core orchestration engine that fetches blockchain data and stores it in a common format. While the indexer code is generic and reusable, a separate instance is deployed for each ledger and network combination (e.g., `uis-indexer-solana-devnet`, `uis-indexer-xrpl-testnet`). |
| **Processor** | Handles business logic and generates standardized events from indexed data. It reads from the Generic Indexer's outbox table and creates customer-facing events. This indexed data is then served by the Events API. |
| **Ledger Adapters** | Chain-specific translation layers that connect to blockchain nodes and convert blockchain-specific data into the unified model consumed by the Generic Indexer. |
| **Ledger APIs** | Provide RESTful interfaces for accessing live blockchain data directly from the nodes. These APIs return stateless data and are called by ledger adapters or other components when checking balances, creating transactions, broadcasting transactions, or getting fees. These are stateless and can scale horizontally. |
| **Node Gateway** | Optional reverse-proxy sidecar automatically co-deployed alongside an EVM indexer when an `authentication` block is present in the ledger's node configuration. It intercepts outbound requests from the Ledger Adapter to the blockchain node and injects the required credentials (static headers, custom JWT tokens), so the indexer itself has no auth-specific code. No additional Helm flag is required — the presence of the `authentication` block triggers deployment. Applies to EVM chains only (Ethereum, Arbitrum, Base, Optimism, Polygon). |
| **Events API** | Provides RESTful interface for accessing indexed data. This is the primary interface where indexed data is accessed by consumers, such as Ledger Accounting. Handles authentication and provides HTTP endpoints for consumers to pull events. Product-level webhook delivery is handled separately by event distribution. |
| **API Gateway** | Single entry point for all UIS communication, routing requests to appropriate internal services. |


## Component requirements

All installed UIS components are required for proper operation. The system is designed as an integrated whole, and removing any component will cause failures.

For example, if you are using Solana:

| Component | Status | Description |
|  --- | --- | --- |
| Generic Indexer (per ledger/network) | **Required** | A dedicated instance orchestrates data fetching for each ledger/network (e.g., mainnet, devnet). |
| Processor | **Required** | Generates events for Ledger Accounting |
| Events API | **Required** | Provides the pull-based interface for event consumption |
| Solana Ledger Adapter | **Required** | Translates Solana-specific data |
| Solana Ledger API | **Required** | Provides access to live Solana blockchain data |


Each component plays a critical role in the data flow pipeline.

## Isolation pattern

The UIS uses the **Bulkhead Pattern** for fault isolation. Three components are run for each ledger and network combination:

- **Ledger API** - Provides access to live blockchain data for that specific ledger/network
- **Ledger Adapter** - Translates blockchain-specific data for that ledger/network
- **Generic Indexer** - Orchestrates data fetching for that specific ledger/network


These three components are duplicated for each ledger and network. This means:

- **Network-level isolation:** If XRPL testnet goes down, XRPL mainnet remains unaffected
- **Ledger-level isolation:** If Solana experiences issues (node failure, resource spike), it does not impact XRPL, TRON, or other ledgers
- **Independent scaling:** Each ledger/network combination can be scaled independently based on its specific needs


This isolation ensures that failures are contained and do not cascade across the system.

## Supported chains and indexer types

Ripple Custody supports a wide range of blockchains. Depending on the chain, indexing is handled either by UIS or by a standalone indexer. For on-premises deployments from v1.26:

- Solana, TRON, and XRPL use UIS automatically and do not require a per-network UIS configuration setting.
- Arbitrum, Base, and Optimism require UIS and must be explicitly configured with `type: "uis"` to be operable.
- Ethereum, Polygon, and Stellar continue to use existing standalone indexers unless you explicitly configure them to use UIS.


## On-premises node requirements

For an on-premises deployment of UIS, you are responsible for provisioning and maintaining your own blockchain nodes. The service must be able to connect to these nodes to index transactions. The following table lists minimum node requirements for on-premises UIS deployments. Check the configuration packaged with your release for release-specific changes.

| Blockchain | Node type | Minimum version | Connection | Archive requirement | Additional details |
|  --- | --- | --- | --- | --- | --- |
| **Arbitrum** | Geth compatible | v3.7.X | JSON-RPC over HTTP | Non-Archive | Debug mode must be enabled. |
| **Base** | Geth compatible | v0.14.1 | JSON-RPC over HTTP | Non-Archive | Debug mode must be enabled. |
| **Ethereum** | Geth | v1.16.4 | JSON-RPC over HTTP | Archive (Required) | Debug APIs must be enabled. High request volume (~1.2k - 300k/hour). |
| **Optimism** | Geth compatible | v1.101511 | JSON-RPC over HTTP | Non-Archive | Debug mode must be enabled. |
| **Polygon** | Geth compatible | v1.16.4 | JSON-RPC over HTTP | Archive (Required) | Debug mode must be enabled. |
| **Solana** | Agave | mainnet 2.3.10 / devnet 3.0.0 | JSON-RPC over HTTP | Archive Recommended | Estimated request volume is ~9000/hour. No debug mode needed. |
| **Stellar** | Horizon | v23.0.0 | HTTP | Archive (Preferred) | Requires `application/x-www-form-urlencoded` content-type for broadcasting transactions. |
| **TRON** | FullNode | v4.8.0 | RPC over HTTP | Archive (Preferred) | Estimated request volume is ~150k/day. |
| **XRPL** | FullNode | 2.6.X | HTTP | Archive Recommended | Request volume is not based on tracked accounts. |


## Configuration

### SaaS deployments

For SaaS deployments, UIS is enabled and managed by Ripple for supported chains.

### On-premises deployments

For on-premises deployments, UIS is available from v1.26 or later.

**Use UIS automatically:**

| Chain | Status |
|  --- | --- |
| Solana | Uses UIS automatically. No per-network `type: "uis"` setting is required. |
| XRPL | Uses UIS automatically. No per-network `type: "uis"` setting is required. |
| TRON | Uses UIS automatically. No per-network `type: "uis"` setting is required. |


**Must be explicitly configured to use UIS:**

| Chain | Status |
|  --- | --- |
| Arbitrum | Requires UIS. Configure the network with `type: "uis"` and provide node access settings. |
| Base | Requires UIS. Configure the network with `type: "uis"` and provide node access settings. |
| Optimism | Requires UIS. Configure the network with `type: "uis"` and provide node access settings. |
| Ethereum | Optional. You can continue using the legacy indexer or migrate to UIS. |
| Polygon | Optional. You can continue using the legacy indexer or migrate to UIS. |
| Stellar | Optional. You can continue using the legacy indexer or migrate to UIS. |


Arbitrum, Base, and Optimism require UIS because there is no supported legacy-indexer path for those chains. They still need explicit configuration in on-premises deployments because Ripple Custody must know which network to run, which node endpoint to use, and which authentication settings to apply.

Other EVM chains like Ethereum and Polygon can optionally use UIS or continue with legacy indexers.

Stellar can also optionally use UIS or continue with legacy indexers.

Migrating a chain to the Unified Indexer is a one-way process and cannot be rolled back.

Please plan your migration carefully. For any questions about the deployment process, please contact your CPE or our Support team.

### Configuration example

To enable UIS for a specific chain, set the `type` parameter to `"uis"` in that network's configuration block within your Helm chart's `values.yaml` file.

The example code below shows the correct structure for enabling the UIS for an Optimism Sepolia testnet:


```yaml
ledgers:
  evm:
    networks:
      - name: optimism-sepolia
        type: "uis"  # Enable UIS for this network
        raw_config: |
          tracker.opindexer-sepolia.base-url = "http://api-management-uis"
          ...
        indexer:
          ...
          node:
            # URL for your provisioned Optimism Sepolia node.
            url: "http://your-optimism-sepolia-node-url"
```

For complete step-by-step migration instructions, including pre-migration setup, database preparation, and validation steps, see the [UIS on-premises migration guide](/products/custody/operations-and-maintenance/upgrading/unified-indexer-service-migration).

### Node authentication for EVM chains

If your EVM blockchain node requires authentication (for example, a Besu node that issues short-lived JWTs, or a third-party provider that expects a static header token), add an `authentication` block to the network's `indexer.node` config. This automatically deploys a Node Gateway sidecar in the same pod as the EVM indexer.

#### Header authentication (static token or API key)

Use `mode: header` to inject a fixed key-value pair into every outbound request:


```yaml
ledgers:
  evm:
    networks:
      - name: mainnet
        type: "uis"
        indexer:
          node:
            url: "https://eth-node-provider.internal/"
            authentication:
              mode: header
              header:
                key: "x-auth-token"
                value: "secret"
```

#### Custom JWT authentication

Use `mode: customJwt` when the node requires a short-lived bearer token fetched from a separate token endpoint. Node Gateway fetches and caches the token, refreshing it before expiry.


```yaml
ledgers:
  evm:
    networks:
      - name: testnet-goerli
        type: "uis"
        indexer:
          node:
            url: "https://example-mas-node.internal/"
            authentication:
              mode: customJwt
              customJwt:
                tokenUrl: "https://example.com/api/generate-access-token"
                tokenMethod: POST
                apiKeyHeader: "x-api-key"
                apiKeyValue: "your-api-key"
                requestBody:
                  signature: "your-signature"
                tokenJsonPath: data
                tokenExpirySeconds: 3600
                refreshBufferSeconds: 300
```

**`customJwt` field reference:**

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `tokenUrl` | String (URL) | Yes | Endpoint that issues the access token. |
| `tokenMethod` | String | Yes | HTTP method for the token request (`POST` or `GET`). |
| `apiKeyHeader` | String | No | Header name to include in the token request for API-key authentication of the token endpoint itself. |
| `apiKeyValue` | String | No | Value for the `apiKeyHeader`. Store this in a Kubernetes Secret and inject via env var. |
| `requestBody` | Map | No | Key-value pairs sent as the JSON body of the token request. |
| `tokenJsonPath` | String | Yes | JSON path expression to extract the token value from the token endpoint response (e.g., `data` or `access_token`). |
| `tokenExpirySeconds` | Integer | Yes | Lifetime of the fetched token in seconds. Node Gateway caches the token for this duration. |
| `refreshBufferSeconds` | Integer | Yes | Seconds before expiry at which Node Gateway proactively fetches a new token. Must be less than `tokenExpirySeconds`. |