# Quickstart

{% hint style="danger" %}
Opening Zephyr in production is in BETA.&#x20;

If you're looking to deploy Zephyr programs and are not already running one (early testers), once you've deployed the program, if you'd like to keep it running you should reach out to the xyclooLabs team either on [xyclooLabs' discord server](https://discord.gg/xRd8DU9j6N) or on the [SDF dev server Mercury channel](https://discord.com/channels/897514728459468821/1149746811519651901).&#x20;

Anyone can deploy, but the xyclooLabs team will currently take the liberty of removing programs that haven't been claimed by the creators as described above, especially if such programs have high resource consumption or heavy, large, and high-frequency database writes.
{% endhint %}

{% hint style="warning" %}
Writing large amounts of data for each ledger will currently result in your program being stopped. Beware that Mercury's cloud environment is still BETA. Once Mercury gets out of BETA release you will be charged according to the amount of data you're writing.
{% endhint %}

{% hint style="info" %}
Note that Zephyr's SDK is still a work in progress, if you have any feature requests please let us know at <https://github.com/xycloo/rs-zephyr-toolkit/issues>
{% endhint %}

In this quickstart, we will build a very simple hello world Zephyr program that writes to a database table for every new ledger close with the message: `"World at ledegr sequence {}", sequence`.

## Prerequisites

Before starting with Zephyr, make sure that you have a Mercury account and have a Mercury API key at hand.

Also, you should have Rust installed. Being familiar with rust is not a strict requirement as working with Zephyr is generally quite easy, but is recommended for a better experience. We also hope to be able to bring more SDKs for other languages in the future!

## Setting up Zephyr.

Before starting, make sure to load your mercury API token as a variable in your current shell. For example:

```
export MERCURY_JWT="ey..."
```

### Installing the Mercury CLI

The Mercury CLI (which currently only has Zephyr functionalities), is an essential tool to interact with Mercury's cloud execution environment. Technically, you could also use the API, but we recommend working with the CLI for ease of development.&#x20;

To install the CLI simply run:

```
cargo install mercury-cli
```

This should install the CLI, you can verify with

```
mercury-cli --version
```

### Setting up the project

The `mercury-cli` takes care of setting up the project for us:

```
mercury-cli new-project --name zephyr-hello-world
```

This will create the starting point for our Zephyr program: set up the `Cargo.toml`, add some compiler flags, a starting point in the `lib.rs` and create the `zephyr.toml`.

### Zephyr.toml

As the last step for setting up our Zephyr program, we need to create and define a `zephyr.toml` configuration file.

This configuration mainly defines the tables your program will read from/write to and their structure. A more detailed explanation about `zephyr.toml` files can be found at [Understanding Zephyr.toml And Database Interactions](/~/changes/KX2wpglbthtd9RsHwz3R/zephyr-full-customization/learn/understanding-zephyr.toml-and-database-interactions.md), but here's the configuration that we will use for this quickstart:

```toml
name = "zephyr-hello-world"

[[tables]]
name = "test"

[[tables.columns]]
name = "hello"
col_type = "BYTEA"
```

The above file means that we're declaring that we may write to/read from a table `"test"` with `"hello"` as the only column during the execution of our Zephyr program.&#x20;

{% hint style="info" %}
Note that `col_type = "BYTEA"` means that we can only store bytes in the database. Currently, this is the only supported data type (even though it will support built-in types in the future). That said, we recommend writing XDR structures to the database to ease decoding the client side with the existing Stellar tooling.
{% endhint %}

## Writing the program

You're now all set to head to the `src/lib.rs` and start building your custom indexer!

As you'll notice, our `lib.rs` comes with prelude imports. These are required by the `DatabaseDerive` macro which we use to describe into a rust type the structure of our table:

```rust
#[derive(DatabaseDerive, Clone)]
#[with_name("test")]
struct TestTable {
    hello: ScVal,
}
```

Here we use the `with_name` attribute to assign the structure to the same table (`"test"`) name we defined in the `zephyr.toml`, and with the `hello` column as struct field.

### Entry point

Now that we've defined the rust-level table that we'll be using, we can start writing the actual ingestion logic. To do so, we move to the already-existing entry point function, i.e. the function the VM will call when executing our program. Inside the function, we also define a new `EnvClient` object, which is the object that will help us communicate with the VM.

```rust
#[no_mangle]
pub extern "C" fn on_close() {
    let env = EnvClient::new();
    
    ...
}
```

The `env` variable now enables us to:

* retrieve XDR data.
* log messages and data.
* interact with the database.
* send web requests.
* read from the ledger.

For this quickstart, we will only be writing to the database, reading raw XDR data, and logging some messages to ensure our program is running correctly.

#### Obtaining the ledger sequence

Our program is called for every new ledger close and is provided with all the changes, transactions, etc that occurred in the ledger. This also includes the ledger sequence kept in the header, which we can easily access thanks to the SDK's meta reader:

```rust
let sequence = env.reader().ledger_sequence();
```

#### Logging

Logging is highly recommended in the current state where local testing is unavailable to all users. Logging can help you understand where the code might be breaking and isolate the cause of the errors. For example, we can now log that we have retrieved a certain ledger sequence:

```rust
env.log().debug(format!("Got sequence {}", sequence), None);
```

In Zephyr, the possible log levels are `debug()`, `warning()`, and `error()`. Each of these methods takes two arguments:

* A string message.
* An optional `Vec<u8>`, designed to log serialized data.

#### Writing to the database

To write to the database (and log accordingly in the meantime) we use:

```rust
...

// Craft the message as ScVal
let message = {
    let message = format!("World at ledegr sequence {}", sequence);
    ScVal::String(ScString(message.try_into().unwrap()))
};

// Create an abstract representation of a
// table row.
let table = TestTable {
    hello: message.clone(),
};

env.log().debug(
    "Writing to the database",
    Some(bincode::serialize(&message).unwrap()),
);
// Insert the row into the table.
table.put(&env);
env.log().debug("Successfully wrote to the database", None);
```

First, we craft the `ScVal` that we will store in the database, then create a new `TestTable` object that represents a row within the table, and insert it into the table using `table.put(&env)`.

### Complete code

This is the final code:

```rust
use zephyr_sdk::{prelude::*, soroban_sdk::xdr::{ScString, ScVal}, EnvClient, DatabaseDerive};

#[derive(DatabaseDerive, Clone)]
#[with_name("test")]
struct TestTable {
    hello: ScVal,
}

#[no_mangle]
pub extern "C" fn on_close() {
    let env = EnvClient::new();

    let sequence = env.reader().ledger_sequence();
    env.log().debug(format!("Got sequence {}", sequence), None);

    let message = {
        let message = format!("World at ledegr sequence {}", sequence);
        ScVal::String(ScString(message.try_into().unwrap()))
    };

    let table = TestTable {
        hello: message.clone(),
    };

    env.log().debug(
        "Writing to the database",
        Some(bincode::serialize(&message).unwrap()),
    );
    table.put(&env);
    env.log().debug("Successfully wrote to the database", None);
}
```

## Deployment

To deploy your program to testnet, you can run

```
mercury-cli --jwt $MERCURY_JWT --local false --mainnet false deploy
```

This will automatically create or replace the tables needed and deploy the program.

## Monitoring the Execution

Now that you've deployed the program, you can head to <https://test.mercurydata.app/custom-ingestion>. You should see something similar to

<figure><img src="/files/TWoERvclixr4euZh1HHB" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
As you can see from the table size, storing information for each ledger execution as we're doing in this program is not recommended. If you're storing new data for each ledger close, then it's likely that what you're doing is replicating a part of ledger functionality and we recommend relying on serverless functions.
{% endhint %}

From the dashboard, you can currently:

* pause/resume the execution of your program.
* see your tables and their space growth.
* start/end logs streaming.

Soon, you'll also be able to manage the tables from the dashboard to add filtering queries or modify their structure on the database (which currently can only be done by reaching out to us). Also, once the projects feature is out, you'll be able to deploy multiple programs with the same account.

That said, to monitor that our program is executing correctly, you can click on "start streaming" and the logs will appear under the "program logs" tab:

<figure><img src="/files/mYQUqxKaGWhPYHvjs6BL" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Logs should only be used to test the correct execution of the program and should not be kept streaming indefinitely. Mercury will automatically empty your logs after a certain threshold.&#x20;
{% endhint %}

## Querying

Our program is writing to a Zephyr table, more specifically in my case the table is `zephyr_85b036892719b0a99aa987b1f62e9b10` . To query the table, you can run the query on Mercury's GraphQL API (in this case, the testnet api):

```graphql
query Test {
  allZephyr85B036892719B0A99Aa987B1F62E9B10S {
    edges {
      node {
        hello
      }
    }
  }
}
```

For example:

```bash
curl 'https://api.mercurydata.app/graphql' \
  -H 'authorization: Bearer YOUR_JWT' \
  -H 'content-type: application/json' \
  --data-raw '{"query":"query Test {\n  allZephyr85B036892719B0A99Aa987B1F62E9B10S {\n    edges {\n      node {\n        hello\n      }\n    }\n  }\n}\n","operationName":"Test"}' \
  --compressed
```

As a response, you should see something similar to:

```json5
{
  "data": {
    "allZephyr85B036892719B0A99Aa987B1F62E9B10S": {
      "edges": [
        {
          "node": {
            "hello": "AAAADgAAACBXb3JsZCBhdCBsZWRlZ3Igc2VxdWVuY2UgMTMxMTY5Nw=="
          }
        },
        {
          "node": {
            "hello": "AAAADgAAACBXb3JsZCBhdCBsZWRlZ3Igc2VxdWVuY2UgMTMxMTY5OA=="
          }
        },
        ...
      ]
    }
  }
}
```

If you decode the contents, you should see the message we encoded in the scval string:

<figure><img src="/files/oPuX3B4eLyQmuMdj2O84" alt=""><figcaption></figcaption></figure>

***

You've now created your first Zephyr ingestion program! From now on the possibilities are endless, and we recommend checking out the next sections of the docs.&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mercurydata.app/~/changes/KX2wpglbthtd9RsHwz3R/zephyr-full-customization/quickstart.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
