# Genaral Info

Welcome to the Mercury docs! If you made it so far congrats, you're one step away from building amazing indexers on Stellar :)

* For custom indexers defined in no time inside your smart contracts head to [Retroshades](/retroshades/introduction-to-retroshades)
* For simple contract event and transaction queries go to [Classic](/mercury-classic/introduction)


# Pricing

Mercury offers four tiers to match teams at every stage.

***

## Dev — Free

For developers exploring Mercury and building on testnet.

* Testnet access only
* Full RPC access (testnet)
* Full REST API (testnet)
* Webhooks with contract ID and topic filtering, HMAC signing, and automatic retry (testnet)
* Retroshades: deploy your own WASM indexing programs that execute against ledger metas and produce custom derived tables (testnet)
* Community support

***

## Builder — $79/mo

For teams running production apps on Stellar that need reliable data access.

* Mainnet + testnet access
* Full RPC access
* Full REST API
* Webhooks with contract ID and topic filtering, HMAC signing, and automatic retry
* No retroshades
* Team support

***

## Pro — $129/mo

For teams that need custom indexing logic on top of Mercury's data layer.

* Everything in Builder
* Retroshades: deploy your own WASM indexing programs that execute against ledger metas and produce custom derived tables
* Team support

***

## Protocol — Get in touch

For protocols and teams with high data throughput or custom infrastructure needs.

* Everything in Pro
* Custom retroshade compute and storage limits
* Priority support
* SLA
* Custom pricing based on usage

Contact us to discuss your needs.

***

## Trial and guarantee

**14-day free trial**: all new teams start with 14 days of full Builder access at no cost. No credit card required.

**Money-back guarantee**: if you are not satisfied at any point during your first two months, we refund everything you have paid, no questions asked.


# Endpoints

### Testnet

| Retroshades/Classic   | <https://testnet.mercurydata.app/rest>                       |
| --------------------- | ------------------------------------------------------------ |
| Smart Account Indexer | <https://testnet.mercurydata.app/rest/smart-account-indexer> |
| Passkey Indexer       | <https://testnet.mercurydata.app/rest/passkey-indexer>       |

### Mainnet

| Retroshades/Classic   | <https://mainnet.mercurydata.app/rest>                       |
| --------------------- | ------------------------------------------------------------ |
| RPC                   | <https://mainnet-rpc.mercurydata.app/>                       |
| Smart Account Indexer | <https://mainnet.mercurydata.app/rest/smart-account-indexer> |
| Passkey Indexer       | <https://mainnet.mercurydata.app/rest/passkey-indexer>       |


# Access & Contact

Reach out on any channel below and we'll get you set up:

* [DM (Telegram)](https://t.me/federicodeponti)
* [Discord server](https://discord.gg/Ez726fg93v)
* [Email](mailto:hello@xycloo.com)


# Service availability

### Health Status

`GET /rest/health` returns the current status of Mercury and its ingestion lag relative to the Stellar network. No authentication required.

\
**Endpoints**

* <https://mainnet.mercurydata.app/rest/health>
* <https://testnet.mercurydata.app/rest/health>

\
**Response**

```json
{
  "service": "up",
  "mercury_latest_ledger": 62458843,
  "stellar_latest_ledger": 62458843
}
```

| Field                   | Description                                                                  |
| ----------------------- | ---------------------------------------------------------------------------- |
| `service`               | `"up"` if all systems are operational, `"down"` if any component is degraded |
| `mercury_latest_ledger` | The latest ledger Mercury has indexed                                        |
| `stellar_latest_ledger` | The current Stellar network tip (from Horizon)                               |

If `mercury_latest_ledger` is significantly behind `stellar_latest_ledger`, Mercury is catching up. Both fields may be absent if unavailable.

### Live status updates

Mercury posts live status updates to the public `live-status` Discord channel. You'll see a message whenever:

* The service/API goes down or comes back online
* Ingestion falls behind the Stellar network tip and later catches back up

Join the server (see [Access & Get in Touch](/genaral-info/access)) and activate notifications for the channel.


# Introduction to Retroshades

Want to get started with retroshades? You are in the right place.

{% embed url="<https://drive.google.com/file/d/1gX7b90q1BYP4dPeAd_VEnfXkXbd5P4bl/view?usp=sharing>" %}

Traditional indexing methods for Soroban events can be time-consuming and complex. They often require developers to:

1. Build customized indexing infrastructure, which can take days or even weeks to implement correctly.
2. Rely on generic indexers with rigid data structures that are a burden to query and require extensive client-side processing.

To counter this, we developed Retroshades to offer a more accessible, and significantly less time consuming alternative that doesn't make efficiency sacrifices:

* **Familiar Territory**: For Stellar native teams, Retroshades equates to building indexing logic with Soroban, eliminating the need of learning anything new.
* **Rapid Implementation**: Developers can create rich, efficient indexers in minutes rather than days. This occurs by leveraging runtime variables and functions from your own smart contract.
* **Streamlined Functionality**: Focused specifically on indexing, Retroshades completely abstracts form the consumer the process of going from a Retroshade event/data structure on your contract to a query-efficient table with native database types as column types.

## What is a Retroshade?

A Retroshade is a contract-level defined data structure that might be though of as a contract event but is not. These data structures are defined and emitted within smart contracts deployed to the Mercury network (or other networks that support the SVM-retroshades-fork) and should not be part of the on-chain contract deployment.

Retroshades are not soroban events because they aren't emitted on-chain, rather they are emitted in retroshade-compatible parallel networks that mirror mainnet, such as the Mercury Retroshades network. This means that these events and all associated data retrieval logic does not live on-chain, but it's developed right within the smart contract, enabling Retroshades to contain significantly more data than a standard event, plus gaining advantage by reusing runtime variables, storage functions, contract calls, contract math functions, etc.

In short, a Retroshade enables you to write fully-featured indexers in minutes as opposed to hours or even days, and that's only possible because developers do not need to learn any new technology, because what you are writing is simply a modified version of your contract that runs on a specialized virtual machine.

## How It Works

The only similar product across all of web3 is [Shadow](https://shadow.xyz) which recently raised 9M from Paradigm. Similarly, Retroshades introduces a novel approach to indexing:

1. **Integration with Smart Contracts**: Developers add the Mercury feature to their Soroban smart contracts and define custom events using runtime variables, contract functions, soroban helpers, etc.
2. **Parallel Execution**: Mercury runs a parallel network alongside the main Stellar network, using a modified version of the Soroban Virtual Machine (SVM) called the Retroshade SVM fork.
3. **Custom Event Emission**: When transactions are executed, the Retroshade SVM emits custom events with rich, structured data.
4. **Backend Database Adaptors**: These events are automatically parsed and stored in a database with native data types and custom columns, making them immediately ready for efficient querying.

### Key Benefits

1. **Ease of Use**: Create complex indexers in minutes instead of days or weeks.
2. **Flexibility**: Fully customizable event structures tailored to your specific needs.
3. **Efficiency**: Native database types and structured columns allow for faster and more intuitive queries.
4. **Familiarity**: Utilize existing Soroban concepts and functions in your indexing logic.
5. **Rich Data**: Incorporate cross-contract calls, storage functions, and external data sources in your indexed events.


# Get Started

This section will guide you through creating a Retroshades indexer.


# Importing the Retroshades SDK

Before starting to emit your retroshades, you need to import the Retroshades SDK within your `Cargo.toml` definition. The SDK will help you define the custom host-functions that are used in the Retroshades-SVM fork, and derive default emit methods for your Retroshade structures.

{% hint style="success" %}
Remember to set the dependency as optional and under a special compile flag. No Retroshade code should be actually deployed on chain. In this example, we define this compile flag as the mercury flag.
{% endhint %}

```bash
[features]
mercury = ["dep:retroshade-sdk"]

[dependencies]
retroshade-sdk = { version = "0.1.0", optional = true }
```

Also make sure to clone the `mercury-cli` repo that will be used to interact with Retroshades

```bash
git clone https://github.com/xycloo/mercury-cli
cd mercury-cli
cargo build --release
```


# Writing and Emitting Retroshades

As promised, this section is very short, because there's little to know about Retroshades: it's all Soroban code!

The only concept that you need to keep in mind is that the retroshade logic within your contract is not metered on chain, and you are not paying any extra fees by adding Retroshade logic to your code. In fact, you can write retroshades for contracts that are already deployed and you don't need to perform any kind of action on-chain.

The contracts compiled with the mercury feature can be directly deployed to Mercury's retroshade network and work out of the box.

## Defining Retroshades

Defining retroshades means defining rust structures. Keep in mind that:

* The fields will be translated to database columns. So if you have data that you want to be able to efficiently query, include them as top-level field, rather than in a nested structure.
* The types are transformed to their corresponding native database type.
* The `contract_id` (string) and `transaction` (transaction hash) fields are injected automatically on every event, alongside `_mercury_event_id`. Do not define them in your struct, otherwise it will cause a conflict.

```rust
#[cfg(feature = "mercury")]
mod retroshade {
    use retroshade_sdk::Retroshade;
    use soroban_sdk::{contracttype, Address, Symbol};

    #[derive(Retroshade)]
    #[contracttype]
    pub struct LiquidityEvent {
        pub from: Address,
        pub kind: Symbol,
        pub amount: i128,
        pub at_fee_per_share_universal: i128,
        pub at_fee_per_share_particular: i128,
        pub at_shares: i128,
        pub new_shares_minted: i128,
        pub ledger: u32,
        pub timestamp: u64,
    }
}
```

## Emitting Retroshades

Emitting a retroshade is as simple as declaring the retroshade structure and emit it in your target code block:

```rust
#[cfg(feature = "mercury")]
retroshade::LiquidityEvent {
    from,
    amount,
    at_fee_per_share_particular,
    at_fee_per_share_universal,
    at_shares,
    new_shares_minted: amount,
    kind: symbol_short!("deposit"),
    ledger: env.ledger().sequence(),
    timestamp: env.ledger().timestamp(),
}
.emit(&env);
```

Keep in mind that you can add additional soroban code under the mercury flag to retrieve the data you need!

```rust
#[cfg(feature = "mercury")]
let (at_fee_per_share_particular, at_fee_per_share_universal, at_shares) = (
    read_fee_per_share_particular(&env, from.clone()),
    get_fee_per_share_universal(&env),
    get_tot_supply(&env),
);
```

This code won't have any impact when the binary is compiled with the `mercury` flag.

{% hint style="info" %}
Tip: beware that .clone() are just compiler stubs in Soroban. So if you end up having to clone `Val` don't worry, because it won't change the resulting binary.
{% endhint %}


# Deploying to Mercury Retroshades

## Compiling with the Mercury flag

Before deploying to the Retroshades network, you need to build the binary that holds retroshades logic:

```bash
cargo build --release --target wasm32-unknown-unknown --features mercury
```

## Deploying

From the `mercury-cli` directory:

{% code overflow="wrap" %}

```bash
./target/release/mercury-cli --base https://mainnet.mercurydata.app/rest \
  --jwt "$JWT" \
  deploy \
    --project example-retroshade \
    --code-path ../retroshades-data-directory/hello_world/target/wasm32-unknown-unknown/release/soroban_hello_world_contract.wasm \
    --contracts 'CNEWCON...'
```

{% endcode %}

The retroshade contract will be executed in real time when `--contracts` are executed. You can target one or more smart contracts for a single retroshade contract.

Deploying with a `--project` name that already exists returns a `409 Conflict`. Use a unique project name per deployment, or delete the existing one first.

## Adding contracts to an existing program

To append additional contract IDs to an already-deployed program without redeploying:

```bash
curl -X POST https://mainnet.mercurydata.app/rest/retroshade/append-contracts \
  -H "Authorization: Bearer <yourjwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "program_id": 1,
    "contracts": ["CNEWCON..."]
  }'
```

This is append-only -- contracts already associated with the program cannot be removed individually. Sending a contract ID that is already registered returns `409 Conflict`.

## Deleting a program

Deletes the program and drops all associated retroshade tables (`retroshade.program_{id}_*`). This is irreversible.

```bash
curl -X DELETE https://mainnet.mercurydata.app/rest/retroshade/1 \
  -H "Authorization: Bearer <yourjwt>"
```

Only the owner of the program can delete it.

## Backfills

Use the backfill feature to retroactively process historical ledgers for a deployed program. Backfills currently run on demand. Contact the Mercury team for access.


# Querying Retroshades

For each retroshade struct defined in the contract, a table on the database will be created that can be queried using `POST /retroshade/query`:

```bash
curl -X POST https://mainnet.mercurydata.app/rest/retroshade/query \
-H "Authorization: Bearer <yourjwt>" \
-H 'Content-Type: application/json' \
-d '{
  "query": "SELECT * FROM retroshade.program_1_liquidity_event LIMIT 50"
}'
```

### Table naming

Tables are named `retroshade.program_{id}_{target}` where:

* `id` is the integer ID assigned to your program at deploy time (returned by `POST /retroshade/deploy` and visible in `GET /retroshade/list`)
* `target` is the snake\_case version of the struct name used in your retroshade emit (e.g. `LiquidityEvent` becomes `liquidity_event`)

To discover the exact table names for your programs, use `GET /retroshade/tables`:

```bash
curl https://mainnet.mercurydata.app/rest/retroshade/tables \
-H "Authorization: Bearer <yourjwt>"
```

Response:

```json
[
  {
    "table_name": "program_1_liquidity_event",
    "project_name_plain": "my-retroshade-program"
  }
]
```

### Mercury-injected columns

Every retroshade table has three columns added automatically by Mercury. Do not define these in your struct:

| Column              | Type        | Description                                              |
| ------------------- | ----------- | -------------------------------------------------------- |
| `contract_id`       | TEXT        | Soroban contract that triggered the emit                 |
| `transaction`       | TEXT        | Transaction hash                                         |
| `_mercury_event_id` | TEXT UNIQUE | Mercury dedup key -- do not use as a semantic identifier |

### List Retroshades Subscriptions

Returns all retroshade programs deployed by the authenticated user.

**Endpoint**: `GET /retroshade/list`

```bash
curl https://mainnet.mercurydata.app/rest/retroshade/list \
-H "Authorization: Bearer <yourjwt>"
```

Response:

```json
[
  {
    "id": 1,
    "project_name": "my-retroshade-program",
    "contracts": [
      "CDVNV4..."
    ],
    "running": true
  }
]
```


# Monitoring Execution

After deploying a Retroshades program you can inspect its runtime status, per-transaction execution history and aggregate statistics through three endpoints.

### Program status

Returns aggregate statistics for a deployed program. Counts are computed over the 500 most recent execution records retained per program.

**Endpoint:** `GET /retroshade/{id}/status`\
**Auth:** JWT required

```bash
curl https://mainnet.mercurydata.app/rest/retroshade/1/status \
  -H "Authorization: Bearer <yourjwt>"
```

Response:

```json
{
  "program_id": 1,
  "project_name": "my-retroshade-program",
  "running": true,
  "last_success_ledger": 62549580,
  "last_error_ledger": null,
  "last_error_message": null,
  "total_executions": 500,
  "total_errors": 0,
  "avg_execution_ms": 3
}
```

### Execution log

Returns a paginated list of per-transaction execution records, newest first. Up to 500 records are retained per program.

**Endpoint:** `GET /retroshade/{id}/executions`\
**Auth:** JWT required\
**Query params:**

| Param         | Default | Max | Description                                           |
| ------------- | ------- | --- | ----------------------------------------------------- |
| `limit`       | 50      | 200 | Number of records to return                           |
| `from_ledger` | --      | --  | Return only records at or before this ledger sequence |

```bash
curl "https://mainnet.mercurydata.app/rest/retroshade/1/executions?limit=10" \
  -H "Authorization: Bearer <yourjwt>"
```

Response:

```json
[
  {
    "ledger_sequence": 62549580,
    "tx_hash": "3e1f7e1ceca397a70d0bb1a2bef884731809b839b865be8fb77bf55ac08abc05",
    "status": "success",
    "error_message": null,
    "rows_written": 2,
    "execution_ms": 4,
    "created_at": "2026-05-13T12:05:36Z"
  }
]
```

`status` is either `"success"` or `"error"`. On error, `error_message` contains the failure detail and `rows_written` will be 0.

### Per-ledger stats

Returns per-ledger aggregate stats for a program, ordered newest first. Useful for tracking throughput and spotting error spikes across ledgers. Records older than 30 days are pruned automatically.

**Endpoint:** `GET /retroshade/{id}/stats`\
**Auth:** JWT required\
**Query params:**

| Param    | Description                                      |
| -------- | ------------------------------------------------ |
| `source` | Filter by `live` or `backfill` (default: all)    |
| `from`   | Return records at or after this ledger sequence  |
| `to`     | Return records at or before this ledger sequence |
| `limit`  | Number of records to return                      |

```bash
curl "https://mainnet.mercurydata.app/rest/retroshade/1/stats" \
  -H "Authorization: Bearer <yourjwt>"
```

Response:

```json
[
  {
    "ledger_sequence": 62549580,
    "source": "live",
    "executions": 3,
    "error_count": 0,
    "rows_written": 6,
    "total_execution_ms": 12,
    "created_at": "2026-06-12T19:26:22Z"
  }
]
```

`source` is `"live"` for normal ingestion and `"backfill"` for backfill runs.

Make sure to change the endpoint if you're on testnet.


# Introduction

Looking to simple yet efficient queries for Soroban data? That's the correct part of the docs.

Mercury allows users to get any **contract event** and **Soroban transaction**, and to create personalized queries to filter the output. We ingest and store all this data for both testnet and mainnet, and provide access for both through our API.

Let's now learn how to performe these queries.


# Queries

Contract events and transactions queries.

{% hint style="info" %}
New queries can be added on request. If you cannot find the appropriate query for what you need, ask on our [Discord server](https://discord.gg/FK28WWHKyb).
{% endhint %}

### Authentication

All queries require an authentication header. You’ll need to provide a JWT token, which will be given to users that have access to Mercury ([get access](https://t.me/federicodeponti)).&#x20;

**Example Authentication Header**

Include the JWT in the request header as follows:

```bash
Authorization: Bearer YOUR_JWT_TOKEN
```

### Pagination & Limits

Paginated endpoints accept the following pagination params:

* `limit`  (default: 100, max: 1000) – requests exceeding the max are clamped to 1000 &#x20;
* `offset` (default: 0)&#x20;
* `cursor` – the id (events) or tx hex (txs) of the last row from the previous page; when set, offset is ignored                                                                                          &#x20;

### Example Variables for Docs

Will be used in the docs for the upcoming examples

```bash
# common vars
BASE="https://mainnet.mercurydata.app/rest"
AUTH="Bearer <your-jwt-here>"
```


# 1. Contract Events

#### 1.1 GET `/events/by-ledger`

Get events for all contracts in a ledger range.

**Query params**

* `from` (i32, optional) – default `0`
* `to` (i32, optional) – default `i32::MAX`
* `limit` (i64, optional) – default `100`
* `offset` (i64, optional) – default `0`
* `cursor` (i32, optional) – last `id` from previous page; when set, `offset` is ignored

**Example**

```bash
curl -X GET \
  "$BASE/events/by-ledger?from=500000&to=500500&limit=50&offset=0" \
  -H "Authorization: $AUTH" \
  -H "Accept: application/json"
```

***

#### 1.2 GET `/events/by-contract/{contract_id}`

This route can do three things depending on the query string.

**1.2.a just the contract**

```bash
curl -X GET \
  "$BASE/events/by-contract/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA?limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

**1.2.b contract + ledger range**

```bash
curl -X GET \
  "$BASE/events/by-contract/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA?from=500000&to=500200&limit=100&cur
  sor=98765" \
  -H "Authorization: $AUTH"
```

You can also do only `from` or only `to`.

**1.2.c contract + topics**

Topics are comma-separated in one param.

```bash
curl -X GET \
  "$BASE/events/by-contract/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA?topics=AAAADwAAAAh0cmFuc2Zlcg==,anotherTopic&limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

**Precedence in this route**:

1. if `from` or `to` → ledger branch
2. else if `topics` → topics branch
3. else → plain

***

#### 1.3 GET `/events/by-contracts`

Takes a comma-separated list of contracts.

**Query params**

* `contracts=ID1,ID2,...` (required) – comma-separated contract `IDs`
* `limit` (i64, optional) – default `100`, max `1000`
* `offset` (i64, optional) – default `0`
* `cursor` (i32, optional) - last `id` from previous page; when set, `offset` is ignored

**Example**

```bash
curl -X GET \
  "$BASE/events/by-contracts?contracts=CONTRACT1,CONTRACT2&limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

***

#### 1.4 GET `/events/by-ledger/contracts`

You can filter by multiple contracts AND a ledger range.

**Query params**

* `contracts=ID1,ID2,...` (required) – comma-separated contract `IDs`
* `from` (i32, optional) – default `0`
* `to` (i32, optional) – default `i32::MAX`
* `limit` (i64, optional) – default `100`, max `1000`
* `offset` (i64, optional) – default `0`
* `cursor` (i32, optional) – last `id` from previous page; when set, `offset` is ignored

**Example**

```bash
curl -X GET \
  "$BASE/events/by-ledger/contracts?contracts=CONTRACT1,CONTRACT2&from=500000&to=500500&limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

***

#### 1.5 GET `/events/by-tx/{tx_hex}`

Get events tied to a single transaction.

```bash
curl -X GET \
  "$BASE/events/by-tx/af01bc..." \
  -H "Authorization: $AUTH"
```

***

#### Response shape

Every route above returns a JSON array of event objects:

| Field              | Type           | Description                                                                 |
| ------------------ | -------------- | --------------------------------------------------------------------------- |
| `id`               | i32            | Mercury row id (opaque, monotonic; usable as a page `cursor`).              |
| `contract_id`      | string         | Emitting contract (`C...`).                                                 |
| `topic1`…`topic10` | string \| null | XDR-base64 event topics. Topics classify an event; they do not identify it. |
| `data`             | string         | XDR-base64 event data.                                                      |
| `tx`               | string         | Transaction hash (hex).                                                     |
| `event_index`      | i32 \| null    | 0-based position of the event within its transaction.                       |


# 2. Soroban TXs

#### 2.1 GET `/txs/by-ledger`

**Query params**

* `from` (i32, optional) – default 0
* `to` (i32, optional) – default i32::MAX
* `limit` (i64, optional) – default 100, max 1000
* `offset` (i64, optional) – default 0
* `cursor` (hex string, optional) – tx field of last row from previous page; when set, offset is ignored

**Example**

```bash
curl -X GET \
  "$BASE/txs/by-ledger?from=500000&to=500500&limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

***

#### 2.2 GET `/txs/by-contract/{contract_id}`

Two behaviors.

**2.2.a just contract**

```bash
curl -X GET \
  "$BASE/txs/by-contract/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA?limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

**2.2.b contract + ledger**

```bash
curl -X GET \
  "$BASE/txs/by-contract/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA?from=500000&to=500500&limit=100&cursor=af01bc..."" \
  -H "Authorization: $AUTH"
```

Precedence:

1. if `from` or `to` → ledger branch
2. else → base

***

#### 2.3 GET `/txs/by-contracts`

**Query params**

* `contracts=ID1,ID2,...` (required) – comma-separated contract IDs
* `limit` (i64, optional) – default 100, max 1000
* `offset` (i64, optional) – default 0
* `cursor` (hex string, optional) – tx field of last row from previous page; when set, offset is ignored

**Example**

```bash
curl -X GET \
  "$BASE/txs/by-contracts?contracts=CONTRACT1,CONTRACT2&limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

***

#### 2.4 GET `/txs/by-ledger/contracts`

Multiple contracts + ledger filter

**Query params**

* `contracts=ID1,ID2,...` (required) – comma-separated contract IDs
* `from` (i32, optional) – default 0
* `to` (i32, optional) – default i32::MAX
* `limit` (i64, optional) – default 100, max 1000
* `offset` (i64, optional) – default 0
* `cursor` (hex string, optional) – tx field of last row from previous page; when set, offset is ignored

**Example**

```bash
curl -X GET \
  "$BASE/txs/by-ledger/contracts?contracts=CONTRACT1,CONTRACT2&from=500000&to=500500&limit=100&offset=0" \
  -H "Authorization: $AUTH"
```

***

#### 2.5 GET `/txs/by-hash/{tx_hex}`

```bash
curl -X GET \
  "$BASE/txs/by-hash/af01bc..." \
  -H "Authorization: $AUTH"
```


# 3. Quick Reference

```bash
# cursor= and offset= are mutually exclusive;
# cursor takes precedence when both are provided
                                                                                                                                                                                   
GET /events/by-ledger
    ?from=&to=&limit=&offset=&cursor=                                                                                                                                            
                                                                                                                                                                                   
GET /events/by-contract/{id}
    ?limit=&offset=&cursor=                                                                                                                                                      
    ?from=&to=&limit=&offset=&cursor=
    ?topics=a,b,c&limit=&offset=&cursor= 
                                                                                                                                                                                   
GET /events/by-contracts
    ?contracts=a,b,c&limit=&offset=&cursor=                                                                                                                                      

GET /events/by-ledger/contracts                                                                                                                                                  
    ?contracts=a,b,c&from=&to=&limit=&offset=&cursor=
                                                                                                                                                                                   
GET /events/by-tx/{tx_hex}                                

GET /txs/by-ledger
    ?from=&to=&limit=&offset=&cursor=
                                                                                                                                                                                   
GET /txs/by-contract/{id}
    ?limit=&offset=&cursor=                                                                                                                                                      
    ?from=&to=&limit=&offset=&cursor=
                                                                                                                                                                                   
GET /txs/by-contracts
    ?contracts=a,b,c&limit=&offset=&cursor=                                                                                                                                      
                                                            
GET /txs/by-ledger/contracts
    ?contracts=a,b,c&from=&to=&limit=&offset=&cursor=
                                                                                                                                                                                   
GET /txs/by-hash/{tx_hex}
```


# Webhooks

Mercury can deliver real-time contract event notifications to your HTTP endpoint via webhooks. When an event matches your webhook's filters, Mercury sends a POST request with\
the event data, signed with HMAC-SHA256 for authenticity.

#### Creating a webhook

`contract_ids` accepts one or more contract IDs. Mercury creates one webhook subscription per contract ID and returns all assigned IDs in the response.

```bash
curl https://mainnet.mercurydata.app/rest/webhooks/new \
  -H 'Authorization: Bearer <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook_endpoint": "https://your-endpoint.com/webhook",
    "contract_ids": ["CDVNV4...", "CDDNV3..."]
  }'
```

Response:

```bash
{
  "ids": [42, 43],
  "secret": "whsec_a1b2c3d4e5f6..."
}
```

Save the secret -- it's shown only once. Use it to verify incoming webhook signatures.

#### Supplying your own secret

By default Mercury generates a random HMAC secret. You can supply your own by passing `webhook_secret`:

```bash
curl https://mainnet.mercurydata.app/rest/webhooks/new \
  -H 'Authorization: Bearer <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook_endpoint": "https://your-endpoint.com/webhook",
    "contract_ids": ["CDVNV4..."],
    "webhook_secret": "my-custom-secret"
  }'
```

The provided value is used as-is and returned in the `secret` field of the response.

#### Topic filtering

By default, a webhook fires for all events from the specified contract. To filter by specific event topics, pass topic1 through topic4 as XDR base64-encoded values:

```bash
curl -X POST https://testnet.mercurydata.app/rest/webhooks/new \
    -H "Authorization: Bearer <your-jwt>" \
    -H "Content-Type: application/json" \
    -d '{
      "webhook_endpoint": "https://your-endpoint.com/webhook",
      "contract_ids": ["CAA3TVC..."],
      "topic1": "AAAADwAAAAhwdXJjaGFzZQ==",
      "topic2": "AAAAEgAAAAAAAAAAYK5vRsE..."
    }'
```

Filtering rules:

* Each topic filter is optional. Omitted topics act as wildcards (match anything).
* All specified topics use AND logic — every filter must match for the event to trigger delivery.
* You can filter on any combination: just topic1, just topic3, both topic1 and topic4, etc.
* Topic values must match exactly (XDR base64 encoding of the ScVal).

#### Listing webhooks

```bash
curl https://testnet.mercurydata.app/rest/webhooks/list \                                                                                                                                 
    -H "Authorization: Bearer <your-jwt>"    
```

Response:

{% code overflow="wrap" %}

```bash
[
    {
      "id": 42,
      "webhook_endpoint": "https://your-server.com/webhook",
      "contract_id": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
      "topic1": "AAAADwAAAAh0cmFuc2Zlcg==",
      "topic2": null,
      "topic3": null,
      "topic4": null
    }
  ]
```

{% endcode %}

#### Deleting a webhook

```bash
curl -X DELETE https://testnet.mercurydata.app/rest/webhooks/{id} \                                                                                                                       
    -H "Authorization: Bearer <your-jwt>"
```

#### Webhook payload

When an event matches, Mercury sends a POST request to your endpoint:

```bash
  {
    "event": {
      "ext": "v0",
      "contract_id": "CDVNV4HBU5WBEQO5K7YZW7NULFLHOTYMUMYUGSPAU2QOADZRIJYTYMM6",
      "type_": "contract",
      "body": {
        "v0": {
          "topics": [
            { "symbol": "deposit" },
            { "address": "GAGAXGWEMQZ4NLHXDRSGXVMX2XENYHQVWSQC5R5U6YNXKBUP7FQ2SCVR" },
            { "i32": 28 }
          ],
          "data": {
            "i128": "100000"
          }
        }
      }
    },
    "tx_hash": "14ff90e64e533d87f5a11a961edd4d7d176e7953767c4532d01e4b0...."
  }
```

#### Headers

* X-Mercury-Signature: HMAC-SHA256 hex signature of the request body
* X-Mercury-Timestamp: Unix timestamp (seconds) when the request was sent
* Content-Type: application/json

#### Verifying signatures

Optionally use your webhook secret to verify that requests are authentic:

*Node.js*

```javascript
const crypto = require('crypto');

  function verifyWebhook(body, signature, secret) {
    const expected = crypto
      .createHmac('sha256', secret)
      .update(body)
      .digest('hex');
    return crypto.timingSafeEqual(
      Buffer.from(signature, 'hex'),
      Buffer.from(expected, 'hex')
    );
  }

  // In your handler:
  app.post('/webhook', (req, res) => {
    const rawBody = JSON.stringify(req.body);
    const signature = req.headers['x-mercury-signature'];

    if (!verifyWebhook(rawBody, signature, 'whsec_your_secret_here')) {
      return res.status(401).send('Invalid signature');
    }

    // Process the event
    console.log('Received event:', req.body.event);
    res.status(200).send('OK');
  });
```

*Python*

```python
  import hmac
  import hashlib

  def verify_webhook(body: str, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode(), body.encode(), hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)
```


# Smart Account Indexer

Mercury runs a free, public indexer for smart accounts (passkey wallets) on Stellar. It indexes the OpenZeppelin smart-account events (context rules, signers, policies) live on both mainnet and testnet, with the full smart-account history already indexed on both networks, and serves them through a REST API compatible with the [smart-account-kit](https://github.com/kalepail/smart-account-kit) `IndexerClient`: point the kit's `indexerUrl` at Mercury and everything works unchanged.

The indexer is currently free to use, and for now requires no authentication or API key.

### Base URLs

| Network | `indexerUrl`                                                 |
| ------- | ------------------------------------------------------------ |
| Testnet | `https://testnet.mercurydata.app/rest/smart-account-indexer` |
| Mainnet | `https://mainnet.mercurydata.app/rest/smart-account-indexer` |

### Usage with smart-account-kit

Set the indexer URL in your kit configuration to the Mercury base URL for your network. Nothing else changes:

```ts
indexerUrl: "https://testnet.mercurydata.app/rest/smart-account-indexer"
```

### Endpoints

All endpoints are `GET`, public, and return JSON.

| Endpoint                       | Description                                                                                |
| ------------------------------ | ------------------------------------------------------------------------------------------ |
| `/`                            | Health check: `{"status":"ok","service":"smart-account-indexer"}`                          |
| `/api/lookup/:credentialId`    | Find smart accounts by passkey credential ID (hex, `0x` prefix optional, case-insensitive) |
| `/api/lookup/address/:address` | Find smart accounts by signer address                                                      |
| `/api/contract/:contractId`    | Full details for one smart account: context rules, signers, policies                       |
| `/api/stats`                   | Indexer statistics: totals, ledger range, per-event-type counts                            |

Example:

```bash
curl https://testnet.mercurydata.app/rest/smart-account-indexer/api/lookup/aabbcc...
```

```json
{
  "credentialId": "aabbcc...",
  "contracts": [
    {
      "contract_id": "C...",
      "context_rule_count": 1,
      "external_signer_count": 1,
      "delegated_signer_count": 0,
      "native_signer_count": 0,
      "first_seen_ledger": 123456,
      "last_seen_ledger": 123789,
      "context_rule_ids": [0]
    }
  ],
  "count": 1
}
```

Lookups match **active** signers only: if a signer was removed, or all of a contract's context rules were removed, the contract no longer resolves through `/api/lookup/*` (and `/api/contract/:contractId` returns 404 once no rules remain).


# Passkey Indexer

Mercury runs a free, public indexer for [passkey-kit](https://github.com/kalepail/passkey-kit) smart wallets on Stellar. It indexes both event generations — the legacy `sw_v1` contracts (live on mainnet and testnet) and the v1 contract vocabulary — live on both networks, with the full wallet history held by Mercury already indexed.

The indexer is currently free to use, and for now requires no authentication or API key.

### Base URLs

| Network | Base URL                                               |
| ------- | ------------------------------------------------------ |
| Testnet | `https://testnet.mercurydata.app/rest/passkey-indexer` |
| Mainnet | `https://mainnet.mercurydata.app/rest/passkey-indexer` |

### Endpoints

All endpoints are `GET`, public, and return JSON.

| Endpoint                       | Description                                                                    |
| ------------------------------ | ------------------------------------------------------------------------------ |
| `/`                            | Health check: `{"status":"ok","service":"passkey-indexer"}`                    |
| `/api/lookup/:credentialId`    | Find wallets by WebAuthn credential ID (hex, `0x` prefix optional, lowercased) |
| `/api/lookup/address/:address` | Find wallets by ed25519 (`G...`) or policy (`C...`) signer address             |
| `/api/wallet/:contractId`      | Full signer state for one wallet, including removed signers                    |
| `/api/stats`                   | Indexer statistics: totals, ledger range, per-event-type counts per generation |

Lookups match **active** signers only. `/api/wallet/:contractId` returns `404` with `{"error":"Wallet not found"}` only when the contract has no indexed signers at all; a wallet whose signers were all removed still returns `200` with tombstoned signers.

Example:

```bash
curl https://mainnet.mercurydata.app/rest/passkey-indexer/api/lookup/1a9eb168624d3ecfeb964f58c5f7ddf00e78fa74
```

```json
{
  "credentialId": "1a9eb168624d3ecfeb964f58c5f7ddf00e78fa74",
  "wallets": [
    {
      "contract_id": "C...",
      "generation": "legacy",
      "signer_count": 1,
      "first_seen_ledger": 54309985,
      "last_seen_ledger": 54309985
    }
  ],
  "count": 1
}
```

`generation` is `"legacy"` or `"v1"` — the generation of the latest event seen for the wallet (an upgraded wallet reads `"v1"`).

### Wallet signers

`/api/wallet/:contractId` returns:

```json
{
  "contractId": "C...",
  "generation": "legacy",
  "first_seen_ledger": 54309985,
  "last_seen_ledger": 54309985,
  "signers": [
    {
      "key": { "type": "secp256r1", "value": "1a9eb168..." },
      "publicKey": "045fb62d...",
      "expiration": 4102444800,
      "expiration_unit": "unix",
      "limits": { "C...": null },
      "storage": "persistent",
      "status": "live"
    }
  ]
}
```

**`key`** — `type` is `"secp256r1"`, `"ed25519"`, or `"policy"`; `value` is the WebAuthn credential ID (lowercase hex) for secp256r1, a `G...` strkey for ed25519, a `C...` strkey for policy.

**`publicKey`** — 65-byte SEC-1 uncompressed secp256r1 public key, lowercase hex. Present for secp256r1 signers only.

**`expiration` / `expiration_unit`** — the raw on-chain number; semantics depend on the generation that wrote the signer: `"ledger"` (legacy) is a ledger sequence, `"unix"` (v1) is UNIX seconds. Both are inclusive: the signer is still valid at the expiration ledger/second itself. Omitted when the signer never expires.

**`limits`** — preserves the on-chain SignerLimits semantics exactly: field omitted = unlimited; `{}` (empty object) = fail-closed deny-all (not the same as unlimited); otherwise an object mapping contract address to either `null` (any key context) or an array of key objects the signer is restricted to.

**`storage`** — `"persistent"` or `"temporary"`.

**`status`** — `"live"`, `"expired"` (active but past expiration, computed per unit against the current ledger or clock), or `"removed"` (tombstone; last-known fields kept). `"evicted"` is intentionally never derived: temporary-storage eviction is not observable from events, so SDKs needing eviction status should probe RPC for the ledger entry.


# Mercury RPC

We make our RPC instance available for our customers to interact with the network/ledger entry lookups. See [endpoints](/genaral-info/endpoints).&#x20;

**example:**

```bash
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
  "jsonrpc": "2.0",
  "id": 8675309,
  "method": "sendTransaction",
  "params": {
    "transaction": "AAAAAgAAAAAg4dbAxsGAGICfBG3iT2cKGYQ6hK4sJWzZ6or1C5v6GAAAAGQAJsOiAAAADQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAACgAAAAVIZWxsbwAAAAAAAAEAAAAMU29yb2JhbiBEb2NzAAAAAAAAAAELm/oYAAAAQATr6Ghp/DNO7S6JjEFwcJ9a+dvI6NJr7I/2eQttvoovjQ8te4zKKaapC3mbmx6ld6YKL5T81mxs45TjzdG5zw0="
  }
}' \
https://mainnet-rpc.mercurydata.app/
```


