# Add Ethereal to Your Exchange

This guide describes how to add EtherealNetwork native token ETRLto your cryptocurrency exchange.

### Node Setup

We highly recommend setting up at least two of your own EtherealNetwork api nodes to give you a trusted entrypoint to the network, allow you full control over how much data is retained, and ensure you do not miss any data if one node fails.

To run an api node:

1. Install the EtherealNetwork command-line tool suite
2. Boot the node with at least the following parameters:

Ethereal-validator \\

&#x20; \--ledger \<LEDGER\_PATH> \\

&#x20; \--identity \<VALIDATOR\_IDENTITY\_KEYPAIR> \\

&#x20; \--entrypoint \<CLUSTER\_ENTRYPOINT> \\

&#x20; \--expected-genesis-hash \<EXPECTED\_GENESIS\_HASH> \\

&#x20; \--expected-shred-version \<EXPECTED\_SHRED\_VERSION> \\

&#x20; \--rpc-port 8899 \\

&#x20; \--no-voting \\

&#x20; \--enable-rpc-transaction-history \\

&#x20; \--limit-ledger-size \<SHRED\_COUNT> \\

&#x20; \--trusted-validator \<VALIDATOR\_ADDRESS> \\

&#x20; \--no-untrusted-rpc

Customize `--ledger` to your desired ledger storage location, and `--rpc-port` to the port you want to expose.

The `--entrypoint` and `--expected-genesis-has`h parameters are all specific to the cluster you are joining. Current parameters for Mainnet Beta

The `--limit-ledger-size` parameter allows you to specify how many ledger shreds your node retains on disk. If you do not include this parameter, the validator will keep the entire ledger until it runs out of disk space. The default value attempts to keep the ledger disk usage under 500GB. More or less disk usage may be requested by adding an argument to `--limit-ledger-size` if desired. Check Ethereal`-validator --help` for the default limit value used by `--limit-ledger-size`. More information about selecting a custom limit value is available here.

Specifying one or more `--trusted-validator` parameters can protect you from booting from a malicious snapshot. More on the value of booting with trusted validators

Optional parameters to consider:

* `--private-rpc` prevents your RPC port from being published for use by other nodes
* `--rpc-bind-address` allows you to specify a different IP address to bind the RPC port

#### Automatic Restarts

We recommend configuring each of your nodes to restart automatically on exit, to ensure you miss as little data as possible. Running the EtherealNetwork software as a systemd service is one great option.

#### Ledger Continuity

By default, each of your nodes will boot from a snapshot provided by one of your trusted validators. This snapshot reflects the current state of the chain, but does not contain the complete historical ledger. If one of your node exits and boots from a new snapshot, there may be a gap in the ledger on that node. In order to prevent this issue, add the `--no-snapshot-fetch` parameter to your Ethereal`-validator` command to receive historical ledger data instead of a snapshot.

If you pass the `--no-snapshot-fetch` parameter on your initial boot, it will take your node a very long time to catch up. We recommend booting from a snapshot first, and then using the `--no-snapshot-fetch` parameter for reboots.

It is important to note that the amount of historical ledger available to your nodes is limited to what your trusted validators retain. You will need to ensure your nodes do not experience downtimes longer than this span, if ledger continuity is crucial for you.

### Setting up Deposit Accounts

EtherealNetwork accounts do not require any on-chain initialization; once they contain some XZO, they exist. To set up a deposit account for your exchange, simply generate a EtherealNetwork keypair using any of our wallet tools.

We recommend using a unique deposit account for each of your users.

EtherealNetwork accounts are charged rent on creation and once per epoch, but they can be made rent-exempt if they contain 2-years worth of rent in XZO. In order to find the minimum rent-exempt balance for your deposit accounts, query the `getMinimumBalanceForRentExemption` endpoint:

curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getMinimumBalanceForRentExemption","params":\[0]}' localhost:8899

&#x20;

{"jsonrpc":"2.0","result":890880,"id":1}

#### Offline Accounts

You may wish to keep the keys for one or more collection accounts offline for greater security. If so, you will need to move XZO to hot accounts using our offline methods.

### Listening for Deposits

When a user wants to deposit XZO into your exchange, instruct them to send a transfer to the appropriate deposit address.

### Validating User-supplied Account Addresses for Withdrawals in XZO

Solana addresses a 32-byte array, encoded with the bitcoin base58 alphabet. This results in an ASCII text string matching the following regular expression:

\[1-9A-HJ-NP-Za-km-z]{32,44}

This check is insufficient on its own as Solana addresses are not checksummed, so typos cannot be detected. To further validate the user's input, the string can be decoded and the resulting byte array's length confirmed to be 32. However, there are some addresses that can decode to 32 bytes despite a typo such as a single missing character, reversed characters and ignored case.

#### Poll for Blocks

The easiest way to track all the deposit accounts for your exchange is to poll for each confirmed block and inspect for addresses of interest, using the JSON-RPC service of your EtherealNetwork api node.

* To identify which blocks are available, send a `getConfirmedBlocks` request, passing the last block you have already processed as the start-slot parameter:

curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlocks","params":\[5]}' localhost:8899

&#x20;

{"jsonrpc":"2.0","result":\[5,6,8,9,11],"id":1}

Not every slot produces a block, so there may be gaps in the sequence of integers.

* For each block, request its contents with a `getConfirmedBlock` request:

curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":\[5, "json"]}' localhost:8899

&#x20;

{

&#x20; "jsonrpc": "2.0",

&#x20; "result": {

&#x20;   "blockhash": "2WcrsKSVANoe6xQHKtCcqNdUpCQPQ3vb6QTgi1dcE2oL",

&#x20;   "parentSlot": 4,

&#x20;   "previousBlockhash": "7ZDoGW83nXgP14vnn9XhGSaGjbuLdLWkQAoUQ7pg6qDZ",

&#x20;   "rewards": \[],

&#x20;   "transactions": \[

&#x20;     {

&#x20;       "meta": {

&#x20;         "err": null,

&#x20;         "fee": 5000,

&#x20;         "postBalances": \[

&#x20;           2033973061360,

&#x20;           218099990000,

&#x20;           42000000003

&#x20;         ],

&#x20;         "preBalances": \[

&#x20;           2044973066360,

&#x20;           207099990000,

&#x20;           42000000003

&#x20;         ],

&#x20;         "status": {

&#x20;           "Ok": null

&#x20;         }

&#x20;       },

&#x20;       "transaction": {

&#x20;         "message": {

&#x20;           "accountKeys": \[

&#x20;             "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX",

&#x20;             "47Sbuv6jL7CViK9F2NMW51aQGhfdpUu7WNvKyH645Rfi",

&#x20;             "11111111111111111111111111111111"

&#x20;           ],

&#x20;           "header": {

&#x20;             "numReadonlySignedAccounts": 0,

&#x20;             "numReadonlyUnsignedAccounts": 1,

&#x20;             "numRequiredSignatures": 1

&#x20;           },

&#x20;           "instructions": \[

&#x20;             {

&#x20;               "accounts": \[

&#x20;                 0,

&#x20;                 1

&#x20;               ],

&#x20;               "data": "3Bxs3zyH82bhpB8j",

&#x20;               "programIdIndex": 2

&#x20;             }

&#x20;           ],

&#x20;           "recentBlockhash": "7GytRgrWXncJWKhzovVoP9kjfLwoiuDb3cWjpXGnmxWh"

&#x20;         },

&#x20;         "signatures": \[

&#x20;           "dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6"

&#x20;         ]

&#x20;       }

&#x20;     }

&#x20;   ]

&#x20; },

&#x20; "id": 1

}

The `preBalances` and `postBalances` fields allow you to track the balance changes in every account without having to parse the entire transaction. They list the starting and ending balances of each account in lamport, indexed to the `accountKeys` list. For example, if the deposit address if interest is `47Sbuv6jL7CViK9F2NMW51aQGhfdpUu7WNvKyH645Rfi`, this transaction represents a transfer of 218099990000 - 207099990000 = 11000000000 lamports = 11 XZO

If you need more information about the transaction type or other specifics, you can request the block from RPC in binary format, and parse it using either our Rust SDK or Javascript SDK.

#### Address History

You can also query the transaction history of a specific address. This is generally *not* a viable method for tracking all your deposit addresses over all slots, but may be useful for examining a few accounts for a specific period of time.

* Send a `getConfirmedSignaturesForAddress2` request to the api node, specifying a range of recent slots:

curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedSignaturesForAddress2","params":\["6H94zdiaYfRfPfKjYLjyr2VFBg6JHXygy84r3qhc3NsC", {"limit": 3}]}' localhost:8899

&#x20;

{

&#x20; "jsonrpc": "2.0",

&#x20; "result": \[

&#x20;   {

&#x20;     "err": null,

&#x20;     "memo": null,

&#x20;     "signature": "35YGay1Lwjwgxe9zaH6APSHbt9gYQUCtBWTNL3aVwVGn9xTFw2fgds7qK5AL29mP63A9j3rh8KpN1TgSR62XCaby",

&#x20;     "slot": 114

&#x20;   },

&#x20;   {

&#x20;     "err": null,

&#x20;     "memo": null,

&#x20;     "signature": "4bJdGN8Tt2kLWZ3Fa1dpwPSEkXWWTSszPSf1rRVsCwNjxbbUdwTeiWtmi8soA26YmwnKD4aAxNp8ci1Gjpdv4gsr",

&#x20;     "slot": 112

&#x20;   },

&#x20;   {

&#x20;     "err": null,

&#x20;     "memo": null,

&#x20;     "signature": "dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6",

&#x20;     "slot": 108

&#x20;   }

&#x20; ],

&#x20; "id": 1

}

* For each signature returned, get the transaction details by sending a `getConfirmedTransaction` request:

curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedTransaction","params":\["dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6", "json"]}' localhost:8899

&#x20;

// Result

{

&#x20; "jsonrpc": "2.0",

&#x20; "result": {

&#x20;   "slot": 5,

&#x20;   "transaction": {

&#x20;     "message": {

&#x20;       "accountKeys": \[

&#x20;         "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX",

&#x20;         "47Sbuv6jL7CViK9F2NMW51aQGhfdpUu7WNvKyH645Rfi",

&#x20;         "11111111111111111111111111111111"

&#x20;       ],

&#x20;       "header": {

&#x20;         "numReadonlySignedAccounts": 0,

&#x20;         "numReadonlyUnsignedAccounts": 1,

&#x20;         "numRequiredSignatures": 1

&#x20;       },

&#x20;       "instructions": \[

&#x20;         {

&#x20;           "accounts": \[

&#x20;             0,

&#x20;             1

&#x20;           ],

&#x20;           "data": "3Bxs3zyH82bhpB8j",

&#x20;           "programIdIndex": 2

&#x20;         }

&#x20;       ],

&#x20;       "recentBlockhash": "7GytRgrWXncJWKhzovVoP9kjfLwoiuDb3cWjpXGnmxWh"

&#x20;     },

&#x20;     "signatures": \[

&#x20;       "dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6"

&#x20;     ]

&#x20;   },

&#x20;   "meta": {

&#x20;     "err": null,

&#x20;     "fee": 5000,

&#x20;     "postBalances": \[

&#x20;       2033973061360,

&#x20;       218099990000,

&#x20;       42000000003

&#x20;     ],

&#x20;     "preBalances": \[

&#x20;       2044973066360,

&#x20;       207099990000,

&#x20;       42000000003

&#x20;     ],

&#x20;     "status": {

&#x20;       "Ok": null

&#x20;     }

&#x20;   }

&#x20; },

&#x20; "id": 1

}

### Sending Withdrawals

To accommodate a user's request to withdraw ETRL, you must generate a EtherealNetwork transfer transaction, and send it to the api node to be forwarded to your cluster.

#### Synchronous

Sending a synchronous transfer to the EtherealNetwork cluster allows you to easily ensure that a transfer is successful and finalized by the cluster.

EtherealNetwork command-line tool offers a simple command, Ethereal`transfer`, to generate, submit, and confirm transfer transactions. By default, this method will wait and track progress on stderr until the transaction has been finalized by the cluster. If the transaction fails, it will report any transaction errors.

Etherealtransfer \<USER\_ADDRESS> \<AMOUNT> --allow-unfunded-recipient --keypair \<KEYPAIR> --url <http://localhost:8899>

The Solana Javascript SDK offers a similar approach for the JS ecosystem. Use the `SystemProgram` to build a transfer transaction, and submit it using the `sendAndConfirmTransaction` method.

#### Asynchronous

For greater flexibility, you can submit withdrawal transfers asynchronously. In these cases, it is your responsibility to verify that the transaction succeeded and was finalized by the cluster.

**Note:** Each transaction contains a recent blockhash to indicate its liveness. It is **critical** to wait until this blockhash expires before retrying a withdrawal transfer that does not appear to have been confirmed or finalized by the cluster. Otherwise, you risk a double spend. See more on blockhash expiration below.

First, get a recent blockhash using the `getFees` endpoint or the CLI command:

Etherealfees --url <http://localhost:8899>

In the command-line tool, pass the `--no-wait` argument to send a transfer asynchronously, and include your recent blockhash with the `--blockhash` argument:

Etherealtransfer \<USER\_ADDRESS> \<AMOUNT> --allow-unfunded-recipient --no-wait --blockhash \<RECENT\_BLOCKHASH> --keypair \<KEYPAIR> --url <http://localhost:8899>

You can also build, sign, and serialize the transaction manually, and fire it off to the cluster using the JSON-RPC `sendTransaction` endpoint.

**Transaction Confirmations & Finality**

Get the status of a batch of transactions using the `getSignatureStatuses` JSON-RPC endpoint. The `confirmations` field reports how many confirmed blocks have elapsed since the transaction was processed. If `confirmations: null`, it is finalized.

curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatuses", "params":\[\["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"]]}' <http://localhost:8899>

&#x20;

{

&#x20; "jsonrpc": "2.0",

&#x20; "result": {

&#x20;   "context": {

&#x20;     "slot": 82

&#x20;   },

&#x20;   "value": \[

&#x20;     {

&#x20;       "slot": 72,

&#x20;       "confirmations": 10,

&#x20;       "err": null,

&#x20;       "status": {

&#x20;         "Ok": null

&#x20;       }

&#x20;     },

&#x20;     {

&#x20;       "slot": 48,

&#x20;       "confirmations": null,

&#x20;       "err": null,

&#x20;       "status": {

&#x20;         "Ok": null

&#x20;       }

&#x20;     }

&#x20;   ]

&#x20; },

&#x20; "id": 1

}

**Blockhash Expiration**

When you request a recent blockhash for your withdrawal transaction using the `getFees` endpoint or Ethereal`fees`, the response will include the `lastValidSlot`, the last slot in which the blockhash will be valid. You can check the cluster slot with a `getSlot` query; once the cluster slot is greater than `lastValidSlot`, the withdrawal transaction using that blockhash should never succeed.

You can also doublecheck whether a particular blockhash is still valid by sending a `getFeeCalculatorForBlockhash` request with the blockhash as a parameter. If the response value is null, the blockhash is expired, and the withdrawal transaction should never succeed.

### Testing the Integration

Be sure to test your complete workflow on EtherealNetwork devnet and testnet clusters before moving to production on mainnet-beta. Devnet is the most open and flexible, and ideal for initial development, while testnet offers more realistic cluster configuration. Devnet features a token faucet, but you will need to request some testnet ETRL to get going on testnet.


---

# 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://ethereals-blockchain.gitbook.io/ethereal-chain-documentation/integrating/add-ethereal-to-your-exchange.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.
