# HollaEx® — The Crypto Exchange Solution

Award-Winning White-Label Crypto Infrastructure & Crypto Business Platform

## Introduction

Before we start, welcome to the **HollaEx Docs!**

With HollaEx, our team hopes to dramatically lower the barriers to entry of crypto exchange ownership, allowing both teams and individuals of all technical abilities to enter the realm of **digital assets, crypto trading, on-ramping, and more.**&#x20;

This documentation contains detailed information and guides for utilizing HollaEx and its many features.

To begin your HollaEx journey, please start by [submitting the form at the following address](https://www.hollaex.com/start). With this form submitted, the HollaEx team will be in touch if we think we can help you and your team.

## Resources & Links

### Our Sites & Information&#x20;

* [HollaEx Website](https://hollaex.com): Our main page on the web. Links and information to various aspects of HollaEx Exchanges :house:
* [HollaEx Pro](https://pro.hollaex.com): An active HollaEx Exchange, run directly by our team. A quick way to experience the user side of the HollaEx experience :chart\_with\_upwards\_trend:
* [HollaEx Blog](https://www.hollaex.com/blog): Collection of articles written by our team and other contributors, discussing a huge variety of topics: new releases, informational posts, developments in the crypto world, and more :newspaper:
* [HollaEx YouTube](https://www.youtube.com/c/HollaEx): Collection of videos related to HollaEx; How-Tos, explanations of systems, News, and more :tv:

### Community

* [Discord Community](https://discord.gg/RkRHU8RbyM): Our Discord. Various boards, covering releases, new tokens, general community chats, and more. Chat with both members of the community as well as team members :speaking\_head:
* [HollaEx Forum for Q/A](https://forum.hollaex.com): Our official forums, focused mainly on support from the community and team members :information\_source:

### Developers

* [API Docs](https://apidocs.hollaex.com/#introduction):  List of the API functions offered with HollaEx exchanges. Set up bespoke functionality with ease :toolbox:


# Launching Your Exchange

{% hint style="info" %}
For additional help setting up, take a look at our walkthrough on [Arcade](https://app.arcade.software/share/FESEgOoldnH9nKwzd78R). :joystick:
{% endhint %}

## Getting an Account

The first step to launching your exchange is to contact our team via the form at the following link. Once reviewed, our team will be in touch with the next steps.

{% embed url="<https://www.hollaex.com/start>" %}

Once we have gone through the initial few steps, you will be provided with credentials that will allow you to log in to the HollaEx Dashboard for the first time, accessed at the link below:

{% embed url="<https://dash.hollaex.com/dashboard>" %}

***

## Logging In

Once on the login page, enter the credentials provided:

<figure><img src="/files/6inKi2aEwgMQXRc2hYJQ" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/9i1f9V0jgxrQSazC4XSp" alt=""><figcaption></figcaption></figure>

A verification code will be sent to your email. Copy this over and enter it to access your dashboard.

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

On the right-hand side of the screen, you will see the setup guide. This will guide you through the required steps to get started.

***

## Backend Setup

On the right-hand side of the screen, you will see the setup guide. This will guide you through the required steps to get started.

On the right-hand side of the screen, you will see the setup guide. This will guide you through the required steps to get started.

<figure><img src="/files/1IZrJ8gZ4JNbdmiVXz7F" alt=""><figcaption></figcaption></figure>

First, click *Open Hosting*. &#x20;

<figure><img src="/files/32Atxvzf3LwHxMjAY2uK" alt=""><figcaption></figcaption></figure>

Then select the domain for the exchange backend by clicking *Configure Domain*.

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

Here you can choose whether to have the free automatically created HollaEx domain or your own (This can be changed at a later date with ease).

{% hint style="info" %}
Note: This domain is **not** the URL that users will use to access the exchange. This is solely for the backend (APIs, core trading operation, etc).&#x20;
{% endhint %}

<figure><img src="/files/0bPNlg9suE0DAvseS7Bk" alt=""><figcaption></figcaption></figure>

Check the domain and save it.

Next, click *Go Live* from the *Exchange Host* page, and confirm the pop-up. This will automatically set up your exchange, generally taking a handful of minutes.&#x20;

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

This setup can be monitored from either the *Recent Deployments* or the *Events* tab.

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

***

## Frontend Setup

Once the backend deployment is complete, access the *Projects* page found on the sidebar; it can also be accessed from the Setup Guide.&#x20;

Create a *New Project* using the button on the top right (if this is greyed out, give the browser a refresh).&#x20;

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

Here a gallery of pre-made frontend templates can be browsed through, and can choose the design that suits your business.&#x20;

This can be changed at a later date.

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

For the fastest setup, stick with the 'HollaEx Kit v2 Web' and click P*ublish* to proceed.&#x20;

<figure><img src="/files/9enijnvUjZKaJj3urdlJ" alt=""><figcaption></figcaption></figure>

Now, click the *Configure Domain* button under *Domain* to set the frontend domain URL.

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

Similar to the backend, a HollaEx domain can be used straight away, or your own chosen one. This can be changed later.

Click *Apply* to save this.&#x20;

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

The website will now publish and then can be reached using the frontend domain address.

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

***

## Accessing the Exchange for the First Time

Once the domain is accessed, you should see a blue screen. Click *Begin Account Creation* to start. <br>

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

First, select an administrator email. This can be, but does not have to be, the same as the dashboard email used.&#x20;

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

Next, create the password for this admin account and proceed.

{% hint style="warning" %}
Note: Ensure both the email and password are saved securely, as they cannot be recovered later.&#x20;
{% endhint %}

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

Confirm the password, and log in with the credentials you just created.&#x20;

***

Next, we have some quick (optional) final steps. Most of the settings, apart from 2FA, can be changed later on.

1. **Time zone & language**: Select the default language and where your exchange will be based.
2. **Admin account security**: Create the 2FA code for login.
3. **Assets**: Choose the initially offered assets.
4. **Features**: Enable or disable the desired features to offer.
5. **Email**: This can be set now, but often easier to do later, following this [guide](/how-tos/set-up-the-smtp-email), so it can be skipped for now.&#x20;

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

Once these are set, review the settings, click *Confirm,* and let the exchange launch.&#x20;

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

Once the setup process is complete, click *Enter Your Exchange* to see the exchange for the first time.

<figure><img src="/files/3Pv5yablSrvH8Q4BSBTp" alt=""><figcaption></figcaption></figure>

With the exchange setup, take a look through some of the other pages of the docs, to learn how to start customization and get your exchange ready for its full launch.

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


# Setting Your Domain

Whilst HollaEx does provide a cloud domain you can use, it is likely that for full user launch you will want your own branded domain. Fortunately, this can be set up in no time at all.

{% hint style="info" %}
No domain yet? If you need to purchase your domain, we would recommend using [Cloudflare](https://www.cloudflare.com/).&#x20;
{% endhint %}

## How to Set Your Exchange Domain

With your domain obtained, it's simple to move from your HollaEx-provided domain to your own.

From the [Dashboard ](https://dash.hollaex.com/dashboard)home, navigate to *Projects* on the sidebar.

Click the Project for which you want to choose the domain.&#x20;

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

On the right side of the page, click *Change* beside the current domain.

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

Select *Use your own domain*.

Input the domain name you own and wish to use.

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

Now, you will be provided with two records to add to your domain's DNS settings.

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

Head over to your domain settings on the registrar's website (Cloudflare in these screenshots).

Find the DNS settings and copy the two records and their details into your DNS settings.

{% hint style="info" %}
If using Cloudflare, ensure that when adding the CNAME record to uncheck the orange cloud proxy icon for DNS proxy protection, as it is not required and will stop the change from working.
{% endhint %}

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

With these two records added, head back to the dashboard and click Verify. HollaEx will automatically check if the records are correct. This may take a couple of minutes to update before the verification will pass.

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

Once verified, click *Apply now*. This will take around 5 minutes to automatically publish, and you can check the progress in the event log.&#x20;

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

When you see the publish as completed, copy the new domain, enter it into the address bar, and see your exchange now launching from your brand's domain.

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


# Frontend and Backend Domains

## What Are the Frontend and Backend?

Every website is made up of two main parts: the *frontend* and the *backend*.

The *frontend* is what most people think of as "the website." It's the visual interface that users see and interact with in their browser.

The *backend* is the engine behind the scenes. It's where the actual logic, data processing, and decision-making happen, invisible to the user, but essential to how the site functions.

A helpful way to picture this is a restaurant. The frontend is the dining room: the space customers sit in, designed to look and feel welcoming. The backend is the kitchen: not visible to customers, but where the real work happens.

### What Is an API?

An *API* is the waiter in this restaurant.

The waiter takes a customer's order (a request from the user) to the kitchen (the backend), waits while the kitchen prepares it, and then brings the finished dish back out to the customer.

In technical terms, an API (Application Programming Interface) is what allows the frontend to communicate with the backend, passing requests one way and delivering responses back the other.

This is also why an exchange may use several different front-end domains. One domain is the one the user sees and types into their browser address bar (the "dining room"), while other, separate domains work behind the scenes to process requests (the "kitchen" and the "waiters" carrying orders between them).

## How These Concepts Relate to Your Exchange

When your exchange is first set up, you will be asked to configure two domains: one for the frontend, and one for the backend.

**The backend API domain** is the kitchen half of your exchange. It is not accessed directly by your end users, and will not lead to any webpage that they use. This can be provided by HollaEx, or you can set it yourself.

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

**The Project page** is where you set up the frontend half of your exchange, the "restaurant" address that end users actually visit to access your exchange. This domain can be provided by HollaEx, or you can set up your own custom domain by following [these instructions](/hollaex-dashboard/setting-your-domain).

<figure><img src="/files/6uHZOdUVSih3C7wD0wVc" alt=""><figcaption></figcaption></figure>

***

## Multiple Projects

Because the frontend and backend are separate, you can create multiple frontends that all draw information from a single backend. This allows you to run different user interfaces, each with different functionality, hosted on different URLs, while only requiring a single HollaEx backend subscription.

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

{% hint style="info" %}
Custom developments provided by HollaEx (such as templates or custom builds) are charged separately from your subscription. If you develop the frontend yourself, this incurs no additional cost.
{% endhint %}


# SMTP Email Setup

## How to Set Up Built-In HollaEx SMTP Email Service

To begin with, you will need to have your exchange hosted on your domain (ie., not using an inbuilt HollaEx one), as you will need to change settings in your DNS records to link everything up.&#x20;

{% hint style="info" %}
If you haven't got your exchange on your domain, check out [this page](/hollaex-dashboard/setting-your-domain) for instructions
{% endhint %}

Assuming you have this, head over to the [HollaEx Dashboard](https://dash.hollaex.com/) and navigate to *Exchange Host* from the sidebar. On the *Domain* tab, on the right side of the screen, enter your domain that the front-end is hosted on.

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

With this done, 3 new records will be displayed in a menu. Simply head over to your DNS records on your domain's provider and add all of them via the settings.

Wait a few minutes for the records to update, and then click *Verify* on the HollaEx Dashboard. The system will automatically check for you, and if it finds the new records, we can proceed and confirm the SMTP setup. You will receive an email notifying you of the email update.

{% hint style="info" %}
If you are running into issues with how to change your records, give the *<support@hollaex.com>* team a message, and they will be able to help get you sorted.
{% endhint %}

***

## Testing the Mail Service

To test the flow, head over to your exchange, *Operator Controls*, then *General* from the sidebar, and finally the *Email* tab.&#x20;

{% hint style="warning" %}
If you are already logged in, you may need to refresh and re-log in to see the changes
{% endhint %}

The necessary email fields will have been filled out for us, and you can test the SMTP by using the test email link at the bottom of the options.

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

Enter the email address you want to send the test to, and within seconds you should see an email from your exchange, with the domain name you chose at the start.

<figure><img src="/files/1iVyEf1Ax1o92XLzTGNS" alt=""><figcaption><p>Note my Exchange's name in the Subject, and the chosen domain in the sender</p></figcaption></figure>

With this confirmed, your exchange is ready to send out any of the [emails that your exchange is configured to do so automatically.](/how-tos/email-customization-and-audit)


# Understanding the Dashboard

Your dashboard is the hub from which you can monitor and alter the behind-the-scenes aspects of your exchange. This doc explains what each section you are looking at can do and how to understand it

## Overview

The *Overview* page is the first shown when logging in. This mostly acts as a quick way to navigate to the other pages and to see the progress of initial setup steps, like setting domains or verifying your identity.&#x20;

The *Exchange* section quickly shows the back-end information and monitors resource usage. The&#x20;

*Frontend Projects* list shows all your current projects and their current status.

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

***

## Backend

This page deals with the 'behind-the-scenes' elements, such as the hosting and API.&#x20;

The main section of the page shows the basic information about the exchange, such as the current version, server location, and more.

Below this, you can monitor current or past deployment activity, such as updates, restarts, domain updates, and more.

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

In the section on the right, you can apply backend changes:

1. The first tab, *Domain*, allows you to change the backend API domain (the setup of which can be seen in [Launching Your Exchange](/hollaex-dashboard/launching-your-exchange) ) and set up your [SMTP Emails](/hollaex-dashboard/smtp-email-setup).
2. The next tab, *Events*, gives a longer list than in the main section of all the previous events that have run on your Exchange.
3. *Monitoring*, the third tab, gives real-time metrics of the Exchange's usage and breakdowns of what specifically is using the most resources in the CPU and memory.&#x20;
   1. Below this is the *Logs* tab, which monitors all activity and can be filtered for the API, Stream, and Plugins
   2. Finally, *Settings* shows the exchange ID (unchangeable) and Display name (which can be changed).
   3. It also shows the region your servers are within and the version.
   4. The blue button allows you to have a backup of the database sent to your email.
   5. The final two sections allow for restarting the exchange, as well as stopping and terminating the exchange in the '*Danger Zone*', actions that should be taken with care.&#x20;

***

## Frontend

### Projects

Here, all your projects are listed, or can be created for the first time as detailed in the [setup guide](/hollaex-dashboard/launching-your-exchange#frontend-setup).&#x20;

From this page, you have similar abilities to those of the backend. On the left side of the screen, you can view relevant details for that project and the most recent deployment actions below.&#x20;

<figure><img src="/files/3z5uFmclzp3IRnUczppS" alt=""><figcaption></figcaption></figure>

With the project(s) set up, you can access that specific project's details by clicking on it.

On the right side, we can change the domain as [described here](/hollaex-dashboard/setting-your-domain).

&#x20;*Access* allows us to define team members from the dashboard and give them a role.

The *Settings* tab is similar to their backend equivalent, showing information and allowing you to unpublish the page, as well as delete that project.&#x20;

<figure><img src="/files/9xs1Zo8one0tmzUlet3f" alt=""><figcaption></figcaption></figure>

### Crypto Gallery

The *Crypto Gallery* allows you to view the various UI templates that can be used with your HollaEx exchange.&#x20;

By default, all exchanges have access to the *HollaEx Kit v2 Web*. If, however, you take a liking to any of the other UIs, send the <support@hollaex.com> to inquire how to get these UIs for your exchange.&#x20;

All current templates can be seen using the try it live button on hover, which sources its data from [pro.hollaex.com](https://pro.hollaex.com).

<figure><img src="/files/99yKPzJB4HrdjNuJKV5A" alt=""><figcaption></figcaption></figure>

***

## Account

*Account* is the final section of the Dashboard, and relates purely to your personal details, rather than direct exchange settings.

### Verification

Here you can run through the KYC (provided by iDenfy, a KYC provider you can use for your own exchange via their [plugin](/plugins/use-plugins/idenfy-automatic-kyc)). This process will be necessary to fully access all features on the dashboard, and you will be reminded to do so by the setup guide on the overview page.

### Agreement

With verification done, the agreement is the final thing to sign to fully complete your setup. Once sent off and verified by our team, you can refer back to it here.&#x20;

### Billing

*Billing* lists your current plan, as well as all previous paid invoices. If your plan is coming up to resubscription time, you are able to do so from here.

### Team

Finally, through the *Team* menu, you can directly invite your team members via email and instantly assign them roles within your organization and exchange.&#x20;


# Operator Control Panel

Found on the blue admin panel on your exchange, the Operator Control Panel is your one-stop shop for monitoring and managing many of the most vital aspects of your exchange, easily and efficiently.

The Operator Control Panel (or admin panel) is the control panel you use as an administrator for your exchange. It's where you can view the full details of your users and their transactions, the total balances of assets, configured values for coins and trading pairs, the status of user activation and verification, and more.

This is the key to controlling and operating your exchange effectively. The operator control panel is accessed through the 'Operator controls' option on the blue admin bar.

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

## Dashboard

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

Opening the Operator Controls will first show the dashboard where you can view a summary of the control panel’s functions. To access other areas, you can use the various menu options on the page, or the options in the sidebar. The functionality of each of these options is explained in the following:

{% content-ref url="/pages/dBYlyFPLWdBKwLukGOqr" %}
[General](/how-tos/operator-control-panel/general)
{% endcontent-ref %}

{% content-ref url="/pages/8bGjdFvCr9cDE0Lo2fEK" %}
[Users](/how-tos/operator-control-panel/users)
{% endcontent-ref %}

{% content-ref url="/pages/9bpeznsZPNnqQjCsa0SL" %}
[User Profile](/how-tos/operator-control-panel/user-profile)
{% endcontent-ref %}

{% content-ref url="/pages/n4cgwbBNSZa1hU4mebbQ" %}
[Assets](/how-tos/operator-control-panel/assets)
{% endcontent-ref %}

{% content-ref url="/pages/Xm3vE6uVj6lihQpOHPM5" %}
[Markets](/how-tos/operator-control-panel/markets)
{% endcontent-ref %}

{% content-ref url="/pages/IdsSfq2eJoppSY33mQWA" %}
[Broken mention](broken://pages/IdsSfq2eJoppSY33mQWA)
{% endcontent-ref %}

{% content-ref url="/pages/QPSmEIuwgpAJNfTbt7jp" %}
[Trading Fees & Account Tiers](/how-tos/operator-control-panel/trading-fees-and-account-tiers)
{% endcontent-ref %}

{% content-ref url="/pages/xICvrhVQiA2LhfQWuX6H" %}
[Roles](/how-tos/operator-control-panel/roles)
{% endcontent-ref %}

{% content-ref url="/pages/QJH5OLsz1RTENgHChPwM" %}
[Chat](/how-tos/operator-control-panel/chat)
{% endcontent-ref %}


# General

General settings include a wide variety of methods of modifying your exchange, from branding, useful links, security, enabled features, user onboarding, email configuration, localization, and help.

## Branding

The general branding preferences of your exchange, such as the exchange name, logo, favicon, loader graphics, as well as the landing page’s background, can be modified in this tab.

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

## Footer

On this page, you can write a description for your exchange, which will be displayed in the right section of the website’s footer area along with your logo.&#x20;

You can also edit the referral badge in the bottom left corner, which can be hidden and the space repurposed for copyright or other business-related data.

All URLs related to your exchange, such as social networking links, GitHub, contact details, API, white paper, etc, where users can get your exchange information, can be added here. These links will be automatically added to the exchange website in the site footer area. If the URL box is left empty, then they will not be displayed.

{% hint style="info" %}
🕹️ Follow our step-by-step guide on Arcade: [Adding Footer Links](https://app.arcade.software/flows/cNLE39af81zbVGme8E1Z/view)
{% endhint %}

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

## Security

Security-related items can be managed from this page. You can add blacklisted countries to block activity on your exchange from certain locations. Google reCAPTCHA configuration can be easily modified as well. You can also invite other exchange operators and specify their [**roles**](/how-tos/operator-control-panel/roles) to help manage your exchange. Finally, API keys can be generated here; see [these pages](/developers/api-guide) for more detail.

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

## Features

Here you can select which features you want to have available on your exchange.

* Pro Trade: Common trading feature where trades are carried out using the orderbook.&#x20;
* Quick Trade: Provides a simpler interface where the markets are visible along with your pricing. In addition, OTC deals will operate via this page.
* Staking: You can add a staking feature to your exchange. This will allow users to lock coins and distribute crypto rewards to stakers.
* Fiat Controls: In-built system for on/ off ramping (only available on *Fiat Ramp* and *DIY Boost* plans).
* Chat system: By enabling this feature, you can allow your users to socialize through chat.
* Homepage: You can add a customizable landing page to make a good first impression to your users.
* Apps: Enable additional functionality for users.

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

## Onboarding

Here you can control the login and sign-up sections of your exchange. You can allow or disallow new user sign-ups by toggling on/off the signup switch as well as choosing whether email verification is a requirement. The onboarding background image can also be uploaded from this page.

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

## Email

In the section for configuring emails, the credentials you have for setting up an SMTP email service can be entered to set up an email service on your exchange.

{% hint style="info" %}
For more information on the email setup, check out the dedicated email pages of the docs: <https://docs.hollaex.com/how-tos/set-up-the-smtp-email>&#x20;
{% endhint %}

Different email types such as login, signup, welcome, bank verification, confirmation email, changing passwords, etc, can have their content customized here as well (as well as the versions for each language on your exchange). &#x20;

Also available is the option to specify an email address that will receive a copy of all important emails sent to your users for audit purposes.

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

## Localization

You can localize your exchange by selecting country, language, and native currency as well as editing visual themes here.

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

## Help Info

You can provide a helpdesk link as well as the link to your exchange’s API documentation in the help pop-up. This help pop-up can be accessed in various areas and will display these helpful links for your users.

{% hint style="info" %}
🕹️ Follow our step-by-step guide on Arcade: [Add Helpdesk Info](https://app.arcade.software/flows/UormE7NFu6aXeNfqBxlZ/view)
{% endhint %}

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


# Users

The User page will allow you to find individual users, monitor their verification status, and access more detailed views of individual users.

Accessing ‘Users’ on the sidebar will open a list of all users on the exchange, from this first page a summary of each user can be seen, and the green 'Go' button will open a detailed view of that specific user (see the [User Profile page](/how-tos/operator-control-panel/user-profile)).

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

New users can be added directly to the exchange with the green button in the top right. All that needs to be provided is an email and password.

## Searching & Filtering

The tools provided above the table of users allow for easy searching for specific users or narrowing down the list to specific criteria.

Multiple filters and search terms can be combined.

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


# User Profile

The user profile gives a detailed view of a specific user and allows a multitude of actions to be carried out on that user's account.

Detailed user profiles are viewable through the "Go" button located on the right side of the ‘**User verification**’ and ‘**All Users**’ lists. Each user's information and preferences can then be viewed and managed.

## About

The ‘About’ tab contains comprehensive information about each user. This data can be modified, by adding or editing user information.

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

• **Trading fee discount:** You can adjust the user’s trading fees by inputting a specified discount. This reduction will be applied on top of the current user trading fee level.

• **Email verification, 2FA:** You can view the status of email verification and 2FA enabling.

{% hint style="info" %}
🕹️ Follow our step-by-step guide on Arcade: [Disabling User 2FA](https://app.arcade.software/flows/5pvxNkMcX9a9zmWfaBUB/view)
{% endhint %}

• **Freeze account:** This prevents the user’s account from performing any actions from then on.

• **Flag user:** Flagging user function can be used to easily track profiles of interest, either for positive or negative reasons (i.e. the user keeps making unusual transactions). You will be able to view the flagged user from the **User list.**

![](/files/TWsZ2mcEp9hYrzWRyt7D)

• **User Identification files:** Files uploaded by the user for the KYC process can be viewed. An admin can also add any applicable files to the user.

• **User Info:** This important section is where a user’s detailed information and verification level are viewed and modified. The higher level that the user is granted, the more advantages that become available. The role of the user can also be assigned here, determining their access level on your exchange.

• **Audit:** The Audit Log records the history of actions taken by the user. By clicking "Download table" on this page, a CSV file of audit records can be downloaded.

• **Login:** The history of records relevant to all logins from the user such as IP address, device, domain, and login time. The total number of logins is counted on the top right of the table. By clicking "Download table" on this page, a CSV file of login records can be downloaded.<br>

## Bank

You can review banking information registered by the user, and provide reasons to the user in case of rejection.

![](/files/DhIZTom8uWUQx9JOg7bc)

## User Balance

The total balances of the assets owned by a user can be viewed here. If you click the ‘+’ button on the left of each asset, the wallet address of that asset that the user has created will appear accordingly.

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

## Orders

Users’ bids and asks on the order book are listed according to different trading pairs. Active orders can be canceled if necessary. By clicking 'Download Table' on this page, a CSV file of the table can be downloaded.&#x20;

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

## Trade History

A full history of the user's orders made on the platform is recorded and displayed. By clicking "Download Table" on this page, a CSV file of the table can be downloaded.

## Deposits

Provides a full history of the user's deposit transactions. Filters can be selected to query the deposits (at least one filter has to be selected to perform a query). By clicking "Download transactions" on this page, a CSV file of the user's deposit transaction records can be downloaded.

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

## Withdrawal

Provides a full history of the user's withdrawal transactions. Filters can be selected to query the withdrawals (at least one filter has to be selected to perform a query). By clicking "Download transactions" on this page, a CSV file of the user's withdrawal transaction records can be downloaded.

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

## Referral

If the exchange has a referral system enabled, here the details for that user can be seen. Who that user was referred by, how many that user has referred, that user's referral link, and a table of users who were referred by that user.

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

## Meta

You can add the user’s metadata from this page. To add new metadata to a user, you need to go to the ‘Configure Meta’ page by clicking the green ‘Configure meta’ button on the right.

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

• **Configure Meta:** On this page, you can add new metadata, and edit and remove currently active metadata. Clicking the ‘Add new meta’ button on the right to add new meta types (string, boolean, number, date-time). The new metadata will be added to all users.

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


# Assets

In the Assets tab, you are able to view the list of coins with the balances you currently have on your exchange, as well as add new ones.

The asset overview page shows what assets are present on the exchange.

You can create and add assets by clicking the ‘create/add asset’ button on the right.

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

{% hint style="info" %}
A detailed explanation of the process of adding a new coin is well explained in the following doc:  <https://docs.hollaex.com/how-tos/add-new-coins-and-pairs>
{% endhint %}

{% hint style="info" %}
Note that adding new coins and markets to the HollaEx Network requires an XHT donation for the activation of the asset. This rule is applied to all coins and markets irrespective of their size and popularity.
{% endhint %}

## Summary

The total balance of all users' wallets is viewable in the Summary tab according to each asset listed in your exchange.

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

## Wallet

Here all wallets and their details can be seen. Search and filter controls allow for finding wallets associated with specific assets, networks, users, addresses, and time of creation.

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

## Balances

This allows downloading CSV files of a user's balances.

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

## Orders

List of all open orders by users on the exchange, filterable by market.

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

## Deposits

The full history of users' deposit transactions is recorded on this page and you can filter the list of records to search for specific deposits. You can select filters to perform a query on the deposits (at least one filter to perform a query has to be selected). A CSV spreadsheet is available by clicking on "Download Table" at the top left of the table.

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

## Withdrawals

The full history of users' withdrawal transactions is recorded on this page and you can filter the list of records to search for specific withdrawals. You can select filters to perform a query on the withdrawals (at least one filter to perform a query has to be selected). A CSV spreadsheet is available by clicking on "Download Table" at the top left of the table.

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

## Earnings

Fees made on your exchange can be settled to a specified user account. Click the ‘Settle’ button and input the email that you would like to send the earnings.&#x20;

The earning history displays the historic settlement of earnings generated from the trading fees of all your users. Earning calculations are dependent on your plan type and membership status. You can view the earnings made between different dates with the filter function.

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

{% hint style="info" %}
[Read more about how earnings are calculated here.](/advanced/revenue-sharing)
{% endhint %}

## Transfers

An admin can transfer a specified amount of assets among existing currencies in your exchange from one user to another by inputting the emails of the two users, both the ‘sender’ email and the ‘receiver’ email. After typing the sender and receiver emails, pick the currency to transfer and enter the amount with a description.

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

## Duster&#x20;

Here the settings for the inbuilt wallet duster that users have access to are configured. By default, the duster will convert small amounts of other assets to XHT, but this can be changed to another asset.

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

## Limits

The Limits tab allows the setting of independent and collective aggregate (total asset amount) daily withdrawals per user tier.

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

#### Independant Limits

This refers to the daily and monthly limits of a given asset. Adding a limit via the green button above the Independent Limits Box will give a menu allowing the choice of which user tier this limit applies to, the particular asset to be limited, and the daily and monthly withdrawal amount of that asset.&#x20;

<figure><img src="/files/UMsPiGc70zKK0Pr93gFE" alt=""><figcaption><p>A example of </p></figcaption></figure>

#### Collective Aggregate Limits

Here, the maximum total amount that can be withdrawn (converted to a chosen asset) over a period, operates similarly to the above menu, with daily and monthly limits chosen. This will then limit the maximum amount calculated over all assets tradable on the exchange

## Fee Markup

The Fee Markup allows you to add a withdrawal fee on top of the default network fee. This offers another way to generate profit on top of the usual trading fees.

By default, these are set to zero, but they can be changed via the orange edit button for each asset on the exchange separately.

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


# Markets

The Markets page lists all the markets currently on your exchange, both those using the Orderbook and the OTCBroker. New markets can also be added to either of these through the Markets page.

## Public Markets

A list of markets that are either incomplete or in pending status waiting for verification, and a list of all active markets that utilize an orderbook for transactions, are displayed in this tab. You can also add a new trading pair by clicking the ‘create/add market’ button. When creating a new market, you are asked to provide certain parameters.&#x20;

{% hint style="info" %}
Read the documentation regarding configuring these pair parameters: <https://docs.hollaex.com/how-tos/configure-pair-parameters>
{% endhint %}

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

## Orders

A list of all current active orders on the exchange, with a filter for selecting markets.

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

## OTC desk

This feature allows you to create any pairing you want, subject to the prices you specify as the exchange operator. For full details on the OTC Broker, please check out the following:

{% content-ref url="/pages/DVKF5sOTlRCyk74JOXXj" %}
[OTC Broker](/how-tos/otc-broker)
{% endcontent-ref %}

<figure><img src="/files/6cgFPLc616cKbwTRDlfK" alt=""><figcaption></figcaption></figure>

## Quick Trade

The Quick Trade tab allows you to configure all deals that use the Quick Trade screen. Using the gear icon, deals can be switched between possible trading methods (Orderbook, OTC, and Network Swap).

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


# Sessions

Monitor and control who is logged into the exchange at any time.

{% hint style="info" %}
*A session* refers to the period in which a user is logged in. \
\
An *active session* means that the user has successfully logged in and is still logged in (if they closed the browser and re-opened the exchange they would not have to log in again).\
\
24 hours after the successful login, the user's session will no longer be *active,* and the user will have to log in again. At this point that particular session will then become *expired.*
{% endhint %}

## Sessions Tab

The first tab, *Sessions*, shows all active and expired logins. For each session the following information is given:&#x20;

* **User ID** - The ID of the user of that particular session. This can be used with the [Users page](/how-tos/operator-control-panel/users) to find the details on that account.
* **Last Seen** - Last time that the user was active (last occurrence of any activity).
* **Session Started** - Time of login for that session.
* **Session Expiry** - The time that the session will expire (this will be 24 hours after the Session start time, and so for active sessions will be in the future).
* **Status** - Whether a session is *active* or *expired*. The operator has the power to revoke a session and log out of the account and set the session to 'expired'.&#x20;

Filter and search tools are provided above the table to help refine results.

Clicking on a particular session will expand the row, and also show:

* Login origin country.
* Login origin IP.
* The device used for login.

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

## Logins Tab

The Login Tab looks fairly similar to the Sessions tab but does differ in what it shows.

* **User ID** - The ID of the user for that login (matching with the email used on the login screen).
* **Time** - Time and timezone of the login attempt.
* **Result** - Whether the login attempt managed to succeed or not. For unsuccessful attempts, the number of unsuccessful attempts will also be noted. This can be used to see if any suspicious attempts of trying to get into an account are occurring.
  * This can be seen in the screenshot below, with the login for User 1, failing twice, before succeeding.
* **Country** - Based on the IP, the country from where the login originated.
* **IP** - IP source of the attempt.
* **Domain** - The domain that the attempt was made from (eg, the exchanges web domain, or if accessed from the app)
* **Device** - Details of what device and browser the attempt was made on.&#x20;

Filter and search tools are provided above the table to help refine results.

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


# Trading Fees & Account Tiers

Tiers are the levels that define what your users are able to do on your exchange, as well as what fees will be applied to them. Account tiers are a great way to reward your most loyal users.

{% hint style="info" %}
:joystick: Follow our step-by-step guide on Arcade: [Adjusting Trading Fees](https://app.arcade.software/share/52yhJSKW6G9US4ENn2hw)
{% endhint %}

## Fees

The trading fees for each pair can be viewed and adjusted in this tab. The maker and taker fees, as percentages, can be adjusted based on user tiers. You can adjust fee values by clicking ‘Adjust fees’ on the right for each pair

* **Maker Fee:** Trading fee applied to the amount traded for limit orders (orders placed and publicly offered on the order book).
* **Taker Fee:** Trading fee applied to the amount traded during a market order (taking from the order book).

<figure><img src="/files/6wrNjjyvJjV6cmZGCMGc" alt=""><figcaption></figcaption></figure>

## Limits

User asset deposit and withdrawal limits can be viewed and adjusted in this tab. You can set the limit of what is allowed to be withdrawn and deposited for each coin on your exchange.&#x20;

All amounts are valued in your set native currency (USDT as default). You can change the native currency for your exchange from the general page (in the localization tab). You can adjust limit values by clicking *Adjust limits* on the right for each asset.

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

## Account Tiers

From this tab, you can add user account tiers and set the trading fees and the deposit and withdrawal limit allowed for each of these tiers.&#x20;

Rules for attaining tiers such as completing KYC, the account’s age, or traded volume can be applied to reward your users. You can add a new tier by clicking the ‘Add tier’ button.&#x20;

{% hint style="warning" %}
Note that once a new user account tier has been added it can't be easily removed.&#x20;
{% endhint %}

The default trading fee will be applied to all trading pairs. You'll be able to define each pair's trading fees in the fees section once the tier has been created.

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


# Roles

The Roles page allows designating trusted members of your community to take part in helping manage your exchange.

{% hint style="success" %}
Enterprise operators have access to a more specialized version of roles, such that they can fine-tune the access and permissions for larger teams. [This can be found at the following doc page.](/how-tos/operator-roles)
{% endhint %}

From this page, you can designate operator roles. You can invite other exchange operators and specify their roles to help manage the exchange. Each role has a different level of access and thus operations possible on your exchange. You can assign operator roles by simply clicking the ‘Add operator’ button.

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

## Role Types

1\. **Administrator -** Can access all areas of the operator controls such as coin creation, minting & burning, trading pairs, and designation of other operator roles.

2\. **Supervisor** - Can access all deposit, withdrawals, and approval settings.

3\. **KYC Role** - Can access required user data to review KYC requirements.

4\. **Communicator** - Has access to website editing tools, with control over aspects such as visuals and text.

5\. **Support**- Can access required user information for user verification.


# Chat

The Chat system will assist greatly in creating a sense of community among your users, encouraging them to invest more in your exchange.

{% hint style="info" %}
The Chat system is available on Crypto Pro, Enterprise, Boost and Voyageer plans
{% endhint %}

The Chat system is where users can chat all day long on your exchange. You can turn the chat system feature on or off, and user chat messages are viewed and managed through this page.

• **Messages:** Users' chat history is monitorable and manageable and displays the most recent chat messages. In case of inappropriate content, the messages can be deleted and the user can be banned from the chat function altogether by clicking the "BAN USER" button.

• **Banned Users:** The list of banned users is displayed here. You can ban users by typing the username of the user to ban and then clicking the ‘BAN’ button. Banned users can be also unbanned with the ‘UNBAN’ button.

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


# Billing

The Billing page gives an easy location to look into both pending and paid invoices.

The top box displays what plan your exchange is using as well as whether pending payments have been made.&#x20;

The tables below give details on what invoices are pending, and the previously paid ones on the next tab.&#x20;

{% hint style="info" %}
To pay for invoices please use your dashboard at <https://dash.hollaex.com/>
{% endhint %}

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


# Customize Exchange

HollaEx offers multiple ways to get the feeling and functionality of your exchange, exactly how you want it

{% hint style="info" %}
📺 Watch on YouTube: [Rebranding & Customization](https://www.youtube.com/watch?v=NLlPZHG8Uys)
{% endhint %}

One of the most important steps when crafting your exchange is to apply your own branding. This goes from the simple- logo, icons, content, and color themes- to more complex tasks like new functionality or more significant front-end changes. &#x20;

Fortunately, the good news is that the HollaEx Kit is very customizable, and you can achieve most ideas you have in mind.&#x20;

There are three main ways to customize your exchange, ranging in complexity to implement:

* [Admin Tools (*Enter edit mode*)](/how-tos/customize-exchange/browser-tools) (Simple to implement)
* [Plugins](/plugins/use-plugins) (Requires developers to implement)
* [Forked Repo](broken://pages/rFFhsrhQvrVrUvprxOfH) (Requires developers to implement)


# Browser Tools

HollaEx exchanges have multiple inbuilt ways to change their visuals. Best of all these changes don't need any coding ability and all are applied without having to restart the exchange

## The Blue Control Panel

Operator control, known as the blue admin panel, is built into the HollaEx Kit exchange and provides a variety of options to customize your exchange.&#x20;

<figure><img src="/files/5RVKZrhJRVt20dY7iDXw" alt=""><figcaption></figcaption></figure>

Each of these options offers a slightly different way to alter the exchange. The following pages go into detail for each:

* [Enter Edit Mode](/how-tos/customize-exchange/browser-tools/enter-edit-mode) - Graphics. Text/ Language & Color themes
* [Operator Controls](/how-tos/customize-exchange/browser-tools/operator-controls-visuals) - General Admin controls, with some more specific customization options
* [Console ](/how-tos/customize-exchange/browser-tools/console)- Inject HTML code, including styles


# Enter Edit Mode

The quickest way to make significant changes to your exchange is by using the Enter Edit Mode button on the bottom left of the screen on the admin account.

On clicking this button, the bottom blue panel will expand, and you will notice various blue icons appear over whatever page you are on.&#x20;

{% hint style="warning" %}
When making new changes, ensure to always use the red ***Publish*** button on the bottom left to save the changes. If you close the browser or use the *Exit edit mode* button, the changes will not be saved.
{% endhint %}

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

## Editing Strings (Text)

### All Strings

<figure><img src="/files/vDQZARIzKlTrokaubAFK" alt=""><figcaption><p>All strings menu &#x26; settings</p></figcaption></figure>

This menu allows altering all the strings (text) on the exchange, as well as access to the language settings.

The menu displays two columns (by default, both will be English until more languages are added) in which all the strings on the exchange can be found, along with the translated counterparts. Language choice can be edited by hitting the arrow on either of the column heads.&#x20;

Using the search bar will filter the list down to strings including that search term for either of the two selected languages.

By clicking any of the strings, a menu will open that will allow editing that particular string, as well as for all other selected languages.

{% hint style="info" %}
Follow our step-by-step guide on Arcade: [Editing Exchange Strings (Text)](https://app.arcade.software/flows/CcAS6sOkzFm34joD1XHB/view)
{% endhint %}

### Settings

Opening the settings menu in the top right will show the languages available on the exchange, and new ones can be chosen from the list. Clicking to the right of the language name will set it as the default (the language displayed when users first land on the exchange). Users can change their language in their settings, or from the dropdown on the right side of the nav bar.

Adding a new language can be achieved by hitting the 'Add Language' button and choosing the language from the dropdown. At present, HollaEx has support for:&#x20;

Arabic, German, English, Persian, French, Indonesian, Italian, Korean, Dutch, Portuguese, Russian, Spanish, Turkish, Vietnamese, Chinese (Simplified), Chinese (Traditional)

To remove a language from open settings, click *remove* and then *confirm*.

### Editing Strings Directly

To change a string on the page you are currently on, rather than using the *All Strings* menu, you can simply click the small, blue pen icon next to any editable string and open its menu directly.

<figure><img src="/files/GOw6OqKzwApcYvEUQCPF" alt=""><figcaption><p>Click this to edit the <em>Wallet</em> string</p></figcaption></figure>

***

## Themes

<figure><img src="/files/l6r99WQDb8v90b7oTO7Y" alt=""><figcaption><p>Themes menu &#x26; settings</p></figcaption></figure>

The first menu that comes up for themes allows settings for the default theme (theme loaded when the user first visits the exchange. Themes can also be removed by clicking the remove button and then confirming.

### Adding New Themes

To add a new theme, click the *Add Theme* button under the list of current themes. The first option will be whether the theme is a *light* or *dark* theme. Simply pick whatever better describes the theme you want to implement, as this setting will set the color of the text to contrast with light or dark background colors.

The next menu is where the magic happens. First, give the theme a name, which will be displayed to users in the theme choice dropdown. Next, the choice of using a *single* or *separate base*. A single base will use a single color as a base and set the theme from this.

The *separated base* gives more control over the specific colors used. For each element, a hex code can be entered into the text box, or by clicking the colored circle and choosing from the color picker, or using RGB code.

The element's names are generally fairly explanatory, but if you aren't sure what something will change, give it a try and look around a few pages to see what it has affected.

***

## All Graphics

<figure><img src="/files/1we5hAdBQAakpTPx5jID" alt=""><figcaption></figcaption></figure>

'*All graphics*' is where every icon, logo, and image can be edited. This works in a similar way to the strings discussed above, but instead of each language, each column is a particular theme. This means that for each theme, a different version of a given graphic can be used, useful if you want to use a recolor for a dark and light theme.

To change a given icon, hit the upload link and choose the desired image.&#x20;

This list can also be filtered using the search bar at the top right.

### Editing Graphics Directly

To change a graphic on the page you are currently on, rather than using the *All Graphics* menu, you can simply click the small, blue upward arrow icon next to any editable graphic and open its menu directly.

<figure><img src="/files/Z8qK8acpp5hkjXBJ6U1J" alt=""><figcaption><p>Click here to edit the <em>Wallet</em> icon</p></figcaption></figure>


# Operator Controls (Visuals)

The Operator Controls are the central hub for all the admin's controls. In this section we will just focus on the controls that deal with aesthetics.

{% hint style="info" %}
For the full list of what can be done in the Operator Controls please look into [these relevant doc pages](/how-tos/customize-exchange/browser-tools/operator-controls-visuals).
{% endhint %}

Inside the Operators Controls, all aspects of your exchange are interacted with. This page will only focus on the few settings located here that allow you to change what it is your users will actually see.

All the following tabs are found in the *General* section of the Operator Controls.

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

## Branding

Branding is the first place where we can change up some exchange settings.

* **Exchange Name** - This is of course your exchanges's name. This isn't actually isn't directly sed on the exchange and so is more in the background than one might think at first glance.
* **Exchange Logo** - This is a bit more important and is the graphic that will represent your exchange in two different places. The first and most prominent is the top left of *every* page on the exchange. Second, a larger version will appear on the Pro Trade market screen. Finally, it will sit in the center of the footer.
* **Loader** - This is the graphic that will display when pages are loading. This is by default a simple spinner design but can be replaced by something else (likely a GIF) with ease.
* **Exchange Favicon** - A favicon is the little icon that appears in the tab of the web browser.
* **Landing Page Background** - Does what it says on the tin, simply changes the background image on the landing page (the page with the ticker and *View Exchange* & *Start Trading* buttons), likely to be the first page users come across
* **Onboarding Background Image** - See [Onboarding ](#onboarding)below.

{% hint style="info" %}
The Theme Specific Graphics link under some of these changeable icons refers to the fact that for each color theme added (see Enter Edit Mode) a different graphic can be used, and will be instantly swapped when changing theme. \
\
This could be simply a recolor of the logo to match a lighter/ darker theme or a new logo entirely.
{% endhint %}

## Footer

The Footer tab offers a handful of customization options to the footer of the website, which is present on every page.

* **Exchange Description** - A short description that sits beside the footer's logo.
* **Footer Small Text** - Used to link to outside pages, where you have detailed your exchanges Terms of services, and privacy policy
* **Referral Badge** - This is a small piece of text in the very bottom left of the footer. By default this links to the HollaEx site, and states 'Powered by HollaEx'. On Crypto Pro, Fiat Ramp, and DIY Boost plans this can be hidden or repurposed.&#x20;
* Footer Links - A flexible system where you can add various columns and populate them with text links to wherever you desire. This could be used to link to your own blogs, about us pages, or any other related resource you want to share with your users.

## Onboarding

Just one visual change here, the Onboarding Background Image. This is the exact same as the landing page but is unsurprisingly the background for the signup and log-in screen.


# Console

The **Console** allows directly adding HTML code into both your website's \<head> and \<body> tags.

{% hint style="info" %}
The code added in the console is injected and run during the website's loading. This injection will work on most browsers, but there may be some limitations for this method using old crawlers and browsers.&#x20;
{% endhint %}

As an example, adding the following code into the \<head> and \<body> sections of the console tab will find a Google font and apply it to all text on the exchange.

## Meta Tags

{% hint style="info" %}
**Cloud** exchanges have an [SEO section](broken://pages/C80DyhD74NPNfjtMgkpq). Adding custom SEO-specific code there is recommended over the process outlined here. This is because Cloud SEO is added statically during the exchange build process and is embedded in the website.
{% endhint %}

For On-Premise operators, SEO tags can be added in the head section.

For a more in-depth description of header tags, W3Schools has good explanations of what can be added:

* [Title tags](https://www.w3schools.com/tags/tag_title.asp)
* [Meta tags](https://www.w3schools.com/tags/tag_meta.asp)

## Styles

HTML can be used to alter the specific styles on the page as well, using both the head and body.

#### \<head>

```html
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lobster">
```

#### \<body>

```html
 <style>
 
* {
  font-family: "Lobster", sans-serif;
}

</style>
```

And here we can compare the two fonts:

### Before

<figure><img src="/files/5FhXHeVVp7sTi9ePfe6y" alt=""><figcaption><p>HollaEx Default Font</p></figcaption></figure>

### After

<figure><img src="/files/121Zr1zdkO9Ts2YEqEOT" alt=""><figcaption><p>Not the most functional font admittedly</p></figcaption></figure>

## More Complex Injections

Above is a fairly simple (but still useful) snippet of code. This could of course be extended to any form of style editing that you desire. For example the following piece of code:&#x20;

```
<script>
function open(url) {
	const a = document.createElement('a');
	a.style = 'display: none';
	a.href = url;
	a.target = '_blank';
	a.rel = 'noopener noreferrer';

	document.body.appendChild(a);
	a.click();
	document.body.removeChild(a);
};

function addItem(id) {
var menu = document.getElementsByClassName('app-menu-bar')[0];
if (id && menu) {
var item = document.createElement('div');
item.id = id;
item.onclick = function () { open('https://etherscan.io/'); };
item.classList.add('app-menu-bar-content', 'd-flex');
var content = document.createElement('div');
content.innerHTML = 'EtherScan';
content.classList.add('app-menu-bar-content-item', 'd-flex');
item.appendChild(content);
menu.appendChild(item);
}
}

function checkMenuItem(id) {
    if (id) {
        if (!document.getElementById(id)) {
            addItem(id)
        }
    }
}

checkMenuItem('etherscan');

setInterval(function() {
    checkMenuItem('etherscan')
}, 1000);
</script>
```

Will add a direct link in the header to EtherScan.<br>

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


# Plugins

Plugins offer a convenient way to add new custom functionality to your exchange

Plugins are JSON files that can easily be installed into your exchange through the Operator Controls. The benefit of Plugins is that they offer a safer environment when modifying your exchange compared to editing the source code.&#x20;

Plugins can be used to add both form and functionality and offer a whole range of potential customizations.

Due to them being a more complex topic, the full [Developing Plugins](/plugins/develop-plugins) section later in this guide provides a deeper dive on this topic.


# Landing Page

A landing page gives a chance to immediately impress your users. HollaEx provides the tools to edit this to your own branding and make the best possible first impression.

{% hint style="info" %}
Want something more unique? Get in touch with <sales@hollaex.com> to discuss the options for using our team to help with development.
{% endhint %}

## How To Edit Your Landing Page

Customising your Landing Page works more or less the same as in the [Browser Tools](/how-tos/customize-exchange/browser-tools) for the rest of the exchange, with a few extra features.

As an admin, to access the Landing Page editing tools, first navigate to the landing page, either by using your base domain or simply by clicking on your exchange logo in the top left.

Once on the landing page, hit the *Enter Edit Mode* blue button in the bottom left.

From here, we will get the same toolbar popping up as on any other page, and we can treat the on-screen pen icons the same.&#x20;

One thing to bear in mind here is that the themes that are present on the rest of the exchange do not apply to the landing page; here, for colours, [the background image will be more important.](#landing-page-background-images)

## Enabling/ Ordering Landing Page Sections

The Landing Page editing does offer a button that is not present on other pages. This can be seen on the far right of the screen in the middle: a blue circle with a gear in it.

This will open the menu in the image below:&#x20;

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

From here we can either arrange the order of the listed features, or from the *Add/remove section* link just above the features themself, we can select to turn certain elements off and on.&#x20;

To change the order, simply drag the feature using the triple lines on the right side.

### Landing Page Sections

* **Title/heading** - This is simply the two lines of text, the button that will link to a market on the exchange, and the image to the right of the text. All of these are editable (and in the case of the text, per language) using the blue pen icons beside each of them.
* **Moving ticker cards** - These are the revolving cards with the assets on your exchange. This will show all assets you have chosen to list, and clicking on each will take the user to that asset's market.
* **Quick Trade Calculator** - This is simply the Quick Trade menu in a widget form, allowing for (even quicker) trading straight from the first page.
* **Q\&A Accordian** - This section allows you to add a simple FAQ section, instantly informing any users with questions that may come in regularly to your support.
* **Call-to-action** - This section offers another simple text section, with a button to access the trading part of the exchange. Check the image section below for information on changing this (and all other sections) background.
* **Mini-convert Tool** - This acts the same as the Quick Trade Calculator above, but is even more streamlined, removing the option to perform the trade, and just showing the prices between the exchange's assets.
* **Key icon features** - The final section displays cards where you can bring attention to whatever you wish. This could be information about your exchange, why users should trust you, or whatever else. Like with all other sections, the images and text can be changed with the pen and upload arrow icons beside the relevant element.

### Landing Page Background Images

Each section can be given its own unique background image, which can be done simply from the small upload arrow in the top right of each section, when the Edit Mode is active.

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


# Fiat Controls

The Fiat Controls give you complete control over your users' crypto/ fiat conversion process

{% hint style="warning" %}
Using the Fiat Controls successfully relies on having access to sufficient liquidity for the chosen crypto and fiat assets.&#x20;

HollaEx **does not provide** liquidity through its liquidity-sharing system for markets utilizing fiat currencies
{% endhint %}

## How the Fiat Controls Work

The *Fiat Controls* is a straightforward tool that puts full control over the fiat-to-crypto conversion (and vice versa) in your hands.

In short, the *Fiat Controls* work as such:

### Depositing

* The user desires a fiat currency to trade into crypto
* User selects *Deposit* on the fiat asset in their wallet, selecting how much they desire to deposit
* The user is shown the exchange bank details
* The user accesses their bank (via banking app, etc.), and transfers the amount to the exchange bank details they were shown
* With the transfer made, the exchange operator will receive a Pending Deposit in the *Fiat controls* found in the operator controls
  * With this notification, one of the exchange teams checks the exchange's bank account and ensures that the requested amount has cleared.
    * If the correct amount has been transferred, the operator can verify the deposit request, and the user will receive the amount of fiat credits minus the deposit fee (defined by the operator). These fiat credits will be *minted* (newly created) to be sent; they are **not transferred from the operator**.
    * If the transfer was not sent, or not enough, the operator can cancel the request and if needed, contact the user via email.

### Withdrawing

Withdrawing works much the same as depositing, but in reverse.

* The user has fiat credits in their HollaEx wallet and wishes to withdraw them to their account
* User selects *Withdraw* on the fiat asset in their wallet, selecting how much they desire to withdraw
  * Before this, the user will have uploaded their bank details to the exchange
* The withdrawal request will appear in the Fiat Controls for the operator
  * This withdrawal request will include the user's bank details, and  the operator will now use the exchange's bank account to transfer the correct amount to the user, from the exchange's bank account
* With this transfer complete, the request can be validated, and the correct amount of the fiat credits for that user will be *burned* (destroyed)

{% hint style="warning" %}
For withdrawing the exchange's bank account must have sufficient, relevant fiat liquidity to serve the expected volume of customers' withdrawal requests
{% endhint %}


# Initial Setup

This page will describe how to add the fiat currency and the required payment accounts  to enable the Fiat Controls for your exchange

## Requirements

* Exchange Bank account (to receive user deposits)
* Liquidity of fiat and crypto in selected markets

***

## Enabling Fiat Controls

Before any other process, ensure that the Fiat Controls are enabled on the exchange.&#x20;

This is done by navigating to the *Operator Controls*, navigating to the *General* page, and then finally to the *Features* tab. Look at the list and ensure that *Fiat Controls* is checked as in the image below.

<figure><img src="/files/ECpiN1edqiFbJMu9g82i" alt=""><figcaption><p>Enabling Fiat Controls</p></figcaption></figure>

***

## Adding Fiat Currency

First, the desired fiat currencies should be added to the exchange. This can be done just as with any other asset. For this example, I will be adding the British Pound (GBP), but the current list of fiat currencies is extensive, and the system is flexible enough to support almost any world fiat currency.

{% hint style="info" %}
If the fiat currency you require is not on the currently supported fiat assets list, please message <support@hollaex.com> to arrange its addition
{% endhint %}

<figure><img src="/files/SIPERt25qKJEFLD01X2j" alt=""><figcaption><p>Where to find fiat assets</p></figcaption></figure>

Click next to see some initial information about the fiat asset, and click confirm to have it added to your exchange.

<figure><img src="/files/LeyyjPHYWpv8NeQ7wGF3" alt=""><figcaption><p>Information menu for fiat currency British pound</p></figcaption></figure>

With this done, the fiat currency can be seen as an option on the user wallet screen.

<figure><img src="/files/yfLTFzTcwAjojYsw9laP" alt=""><figcaption><p>Viewing GBP in the wallet</p></figcaption></figure>

{% hint style="info" %}
Note that although the fiat asset has been added and will be visible in users' wallets, currently, there will be no way for it to be traded on the exchange. First, a market must be added and set up for the relevant pairing; this is detailed on the [following page.](/how-tos/fiat-controls/setting-up-fiat-on-off-ramp)
{% endhint %}

***

## Adding Payment Account

A *payment account* is the type of account that users will fill out on your exchange, so that it is possible to allow them to withdraw any fiat credits they have.&#x20;

This payment account can also be used for on-ramping, discussed in the [next section](#adding-exchange-on-ramp).

To add your payment account, this is done by accessing the *Operator Controls*, then *Fiat Controls* in the sidebar, and finally the *Payment Accounts* tab at the top of the page.

To get started, click either of the green buttons labeled *Add payment account.*

<figure><img src="/files/BVsj11lT4cEMZgAbfeNS" alt=""><figcaption><p>Adding user payment account</p></figcaption></figure>

Choose the method of payment account:

* **Bank**: This will provide a default set of fields to fill in, such as bank name, IBAN, etc. These fields can be fully edited, or removed, as well as new fields added
* **PayPal**: Simple email
* **Custom:** This functions very similarly to the Bank setting above, but suggests no default fields

{% hint style="info" %}
When adding multiple Payment accounts for different currencies, ensure to use *Custom* for any currencies after the first, even if you are creating an account for a bank&#x20;
{% endhint %}

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

Selecting this, we will see the following default options:

<figure><img src="/files/LsCTGt6iCxzNrQOvQOJE" alt=""><figcaption><p>Default Payment Account options</p></figcaption></figure>

Here, we have the option of setting exactly what details we want from users. Continuing with the UK example above, in addition to the account number (which we already have), we would also need users to provide their sort code.

To do this, select *Add more payment details* at the bottom left of the options. This will open the menu that can be seen in the following image. Enter the field's name and whether or not it is required.

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

With this added, we can save the Payment account.

<figure><img src="/files/RJyWUIqXUyaVS9q8tj3P" alt=""><figcaption><p>Finished payment account, your specifics may be different</p></figcaption></figure>

## Adding Exchange On-Ramp&#x20;

With the fiat asset now active on the exchange, we have to enable a method for users to deposit the fiat to the exchange, and thus be *minted* credits they can use to trade.

To do this, the details of the location we want users to send fiat to will be displayed to users.

This is done by accessing the *Operator Controls*, then *Fiat Controls* in the sidebar, and finally the *On-Ramp* tab at the top of the page.

On this page, we will see a list of all the on-ramps we have created for all the fiat assets the exchange has listed.

At the moment, my page is empty, so let's add an on-ramp for GBP (British Pound). This can be done in two ways: either by clicking *Add on-ramp* in the top right and selecting from the dropdown which asset to add the on-ramp, or directly selecting the *Add on-ramp* button beside the relevant asset.

<figure><img src="/files/aLm5KkBSWal7dmPqXEqE" alt=""><figcaption><p>Adding on-ramp options </p></figcaption></figure>

The first menu is the selection of what type of On-Ramp to provide:

We are given the option to select from the pre-made payment account that we created in the previous section, and we will then be given those fields we set up. This will likely be the option most operators use.

Alternatively, we can select PayPal, or select Custom and create a new method as shown below:

<figure><img src="/files/2OnsZcmeVj2dRInqthXZ" alt=""><figcaption></figcaption></figure>

With the name chosen and NEXT clicked, we will see the following back on the On-Ramp page:&#x20;

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

Now we have to add the relevant details for the currency we have chosen.&#x20;

In this case, for GBP, to transfer between accounts, we require:

* Name
* Account Number
* Sort Code&#x20;

{% hint style="info" %}
*Sort code* is a UK-specific requirement for domestic transfers.&#x20;

This illustrates how the HollaEx system gives the flexibility to ensure that whatever national method is used, the users will be provided with the relevant information they need.

The exact fields you add will vary depending on the fiat in question
{% endhint %}

Adding these 3 fields, we can set them as required. This means that the On-Ramp cannot be created without them. Alongside these required fields, we can add optional fields as required. These are simply those fields that are not strictly required to instantiate the On-Ramp.&#x20;

Below we can see my on-ramp for a British bank, with some credentials added.&#x20;

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

And in the following image, how these details are displayed to users on the Deposit screen for GBP. This is covered in the [Users Making Fiat Deposit page](/how-tos/fiat-controls/users-making-fiat-deposit).

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


# Setting Up Fiat On/ Off Ramp

By using the inbuilt HollaEx OTC Broker, you can offer your users full fiat-to-crypto conversion (and vice-versa)

{% hint style="warning" %}
Due to the nature of how the OTC Broker sources funds, sufficient liquidity is required for **both** the crypto and fiat assets
{% endhint %}

## Setting Up the OTC Broker for Fiat

{% hint style="info" %}
The Fiat Controls on/ off ramping method uses the OTC Broker. \
\
The process is similar to crypto-to-crypto OTC deals, which is covered in detail on the following doc page. In comparison, this guide will be briefer, focusing on the fiat-specific elements.
{% endhint %}

First, navigate to the *Markets* page in the *Operator Controls*. Following this, head to the *OTC Desk* tab and click *Start New Deal*

<figure><img src="/files/8FTwWG29H5Ikby8pEGXf" alt=""><figcaption><p>Starting new OTC deal</p></figcaption></figure>

In this menu, choose the crypto and fiat assets you wish to allow users to convert between:

<figure><img src="/files/725r0aldjDM0iY9b8fws" alt=""><figcaption><p>Selection for ramp between BTC and GBP</p></figcaption></figure>

{% hint style="info" %}
For fiat conversion, ensure that your chosen fiat currency is in the *Priced* drop-down on the right. This will make selecting parameters and pricing simpler in the next steps
{% endhint %}

The next menu asks for the parameters of the deal. This is simply the lowest and highest amount of the base asset that can be traded for.

<figure><img src="/files/5gCDH5nsQiLqsZvFVUvD" alt=""><figcaption><p><em>In this case no deal can be less than 0.001 BTC or above a full BTC.</em></p></figcaption></figure>

### Pricing

* For fiat to major markets (BTC, USDT, ETH) ramps, we would recommend selecting *Dynamic* pricing in the *Type* dropdown. This means you won't have to regularly monitor and reset the buy/ sell prices. This is what we will continue with
* If you wish to allow conversion between your token and fiat, *Static Pricing* may be more suitable.
  * However, ensure to consider which method is best for your situation.

{% hint style="info" %}
For a more detailed breakdown of pricing, please [read the pricing section of the main OTC Broker page](/how-tos/otc-broker#asset-pricing-method)
{% endhint %}

If the system can find the appropriate market on a major, this will be set as the default, and now I know that every five seconds, the price will be set to the current market value, without me having to lift a finger.

<figure><img src="/files/eTtzQFatUU3LkbMGdiz9" alt=""><figcaption><p>Price settings for BTC/GBP</p></figcaption></figure>

{% hint style="info" %}
In the image above with BTC/GBP, I have my price set to monitor the Binance BTC/GBP price, then apply a 5% spread, with the resulting buy and sell prices (at that moment) shown by the orange arrow
{% endhint %}

### What if My Fiat Market Isn't Supported?

In the case that you are using a smaller national fiat, likely, larger exchanges may not directly support it, and thus, the HollaEx system won't be able to find an appropriate source of pricing data.

This can be resolved by using an *Advanced* link and inputting your formula, which follows the USD market (which the system will likely be able to find), and then converting to the relevant rate between your desired fiat and USD.

#### Example

Let's consider a case where I have decided to add the UAE Dirham to my exchange and want to allow conversion to BTC with it. As shown in the image below, the system has not been able to find a relevant BTC/AED market.

<figure><img src="/files/5D5Eve0pfmCyV8tx1sif" alt=""><figcaption></figcaption></figure>

To resolve this, I will select the exchange I want to use to dynamically set the price. In this case, I will stick with Binance, and then click the *Track Market Price* box. I can search the options, find the BTC/USDT price, and select this.

Since I will compare to USDT, this will follow the USD price as well. See in the image below how the my *Formula* field displays '*binance\_btc-usdt'*.

<figure><img src="/files/2b1XoIQn64xv2ShZ92la" alt=""><figcaption></figcaption></figure>

Now the Advanced button must be used to change the price from dollars to UAE Dirham. Opening the *Advanced* field, the *binance\_btc-usdt* formula will already be there, and to convert to Dirham, a quick look at a currency conversion site tells me that (at the time of writing) 1 AED is equal to 0.27 USD.

So, to get a value for BTC/AED I simply have to divide the current formula by 0.27, and I will get the correct value for BTC/ AED

<figure><img src="/files/hDotYXCAxEwIvu31hSbW" alt=""><figcaption><p>Addition underlined in orange</p></figcaption></figure>

Now, when we ask for the price result, we see that I am getting the correct value in AED (at the time of writing), with the spread applied.

<figure><img src="/files/92vBgm2BVGmo7riCeuPJ" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Ensure to monitor the value of the currency rates and update accordingly. Fiat rates are fairly stable generally and can be buffered with the spread, but for the best performance, ensure they are kept at the correct value.
{% endhint %}

## Next Options and Editing Existing Deals

From here, all the options are the same as for standard crypto-only deals, so check out those pages for how to progress the next couple of menus, and for editing/ removing existing deals.

{% content-ref url="/pages/DVKF5sOTlRCyk74JOXXj" %}
[OTC Broker](/how-tos/otc-broker)
{% endcontent-ref %}


# Editing Deposit & Withdrawal Fees

To select the fees for each of the fiat assets you offer on the exchange, head over to the \
*Operator Controls*, then *Fiat Controls* on the sidebar, and select the final tab, *Fiat Fees.*&#x20;

<figure><img src="/files/60MtK6uMxfRyn1JkOLwe" alt=""><figcaption></figcaption></figure>

Here, the default fees of each fiat currency can be overwritten by selecting the orange Edit button for the fiat currency.

<figure><img src="/files/fP3yuje3c0nAuFLbfytD" alt=""><figcaption><p>The editing fee menu</p></figcaption></figure>


# Users Making Fiat Deposit

With the Initial Setup complete, as well as the first Fiat/ Crypto Ramp created, we can focus on the flow that will be followed by users to get fiat credits into their HollaEx wallet.&#x20;

On the deposit page for the fiat currency, users will see the details of the setup on-ramp, similar to the image below.&#x20;

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

With this done, the user enters how much fiat they wish to deposit. In the following guide, I have requested £1000 (GBP).

On the deposit menu, shown below, the most important field for the user is the *Transaction ID.* This is the reference that the user leaves when transferring the fiat to the account shown in the *Bank* field.

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

The next menu gives a final summary of the choices.

<figure><img src="/files/0f60dbdfD821hjZtrK84" alt=""><figcaption></figcaption></figure>

With the above details, the user must now come away from the exchange and deposit with exactly the details given above, in the manner they would do normal bank transfers.

Clicking *Proceed*, they will receive the following information. The time to clear is dependent on your team's response time on the next steps.

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

In addition, we can see the GBP deposit status on the *History* page, in the *Deposits* tab, and finally, the user will receive an email notifying them of the deposit request.

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

***

## The Admin Role in User Fiat Deposits

Now that the user has submitted their deposit request, as the admin, we will be able to see on the Summary page of the  *Fiat Controls* a deposit request. There will also be an email sent to the admin email to notify them of the request.&#x20;

We can see in the image below that the request we made from the user side and the transaction ID.

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

Now all that must be done is to check the account that we asked users to deposit into. If we can see the funds with the appropriate ID and amount, we can simply *validate* the request. If not, it can be *denied.*

On validation, the admin can edit the reference and add a description, and then the exchange will mint an appropriate amount of fiat credit and send it to the requesting user.

***

## User Receiving Fiat

With the admin validating the requests, we can hop back to the user and see now in deposits that the GBP deposit has been completed.&#x20;

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

As well as this deposit being reflected in the wallet.

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


# Users Trading With Fiat

With the users now having fiat credits in their wallets, the next logical step is to trade with it. This will be done using the [Ramp that was set up previously](/how-tos/fiat-controls/setting-up-fiat-on-off-ramp).&#x20;

The Ramp can be found in the Quick Trade screen, and then by selecting the desired Fiat currency, and finally the desired crypto to trade for. In the image below we can see that I have two options for GBP, BTC, and USDT.

<figure><img src="/files/GQ0AxZEFfQE1nALjLyyr" alt=""><figcaption><p>Selecting Crypto to convert to from fiat</p></figcaption></figure>

From this point on the process is just as with any other OTC Deal. As long as the user has enough of the converting asset, and crucially, the exchange's source wallet has enough of the desired asset, the trade will occur instantly with zero fees incurred for anyone.&#x20;

Going through this flow as an example, let's request changing 10GBP to the correct amount of USDT (that being the price taken at the time of trade from Binance's GBP/ USDT market, minus the spread set in the Operator Controls).

<figure><img src="/files/qUGBmScwOHi7riQJtJO0" alt=""><figcaption><p>Filled in OTC Fiat Screen</p></figcaption></figure>

Clicking R*eview Order* above, we will see the pop-up confirmation screen below. The user will have a set amount of time to confirm the order at that price (this time is set in the OTC dynamic pricing section on creating the deal by the operator).

<figure><img src="/files/DTLp01Pj7F1goPMiGToR" alt=""><figcaption><p>Confirmation Screen</p></figcaption></figure>

Finally, we get the success screen, and now the user has USDT in their wallet, and the source has 10 GBP credits.

<figure><img src="/files/MWHof8OSZ0EMa2wn9v8X" alt=""><figcaption><p>Success screen</p></figcaption></figure>

***


# User Making Fiat Withdrawal

When a user decides to withdraw fiat that they have on their wallet, this operates on a very similar method to depositing but simply in reverse.

Withdrawals start from the user's wallet page and select the *Withdraw* option on the fiat credit they have.&#x20;

<figure><img src="/files/HTGbq9uyuwRhv7ryxNGH" alt=""><figcaption><p>Where to withdraw fiat for users</p></figcaption></figure>

After this, assuming they have set up their bank details on the *Identification* page, they will see the following simple page (If the user has not completed the verification page they will be prompted to do so).

On this page, the Bank will be auto-filled if they only have a single option, or a drop-down to select if they have more than one.

After this, they select how much of their fiat to withdraw and click *Review Withdrawal*.

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

The user will be presented with a final screen showing them the details of the withdrawal, the total amount (including the withdrawal fee), as well as what account will be receiving the withdrawal.

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

The pending withdrawal is now also viewable on the *History* page, and the user will receive an email notification of the request. To complete it the admin will need to validate it.

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

***

## Admin Validating Withdrawal Requests

Whenever a user submits a withdrawal request, as shown above, the admin will receive a notification email, and similarly to the deposit case, will also see it present in the Operator Controls Fiat Controls, as seen below.

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

By clicking the + button beside the newly arrived withdrawal request we can see the details.&#x20;

<figure><img src="/files/9gejpqKPDfQtNGXcYWec" alt=""><figcaption></figcaption></figure>

In particular, we will see the bank details in the Description section of the request. At this point as the operator it is your responsibility to transfer the funds using your banking method etc. outside the exchange and ensure that the correct amount of fiat has been sent to the correct user account.

With this transfer taking place, can hit validate and confirm the withdrawal, adding/ editing any of the IDs or transactions.

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

***

User Successful Withdrawal

With the admin validating the withdrawal, taking a look again at the user's page, we can see that in history the withdrawal has been complete.

<figure><img src="/files/dbnyVM8UWM7ZFekQD8LG" alt=""><figcaption><p>Withdrawal page after validation</p></figcaption></figure>

And likewise the wallet has decreased the correct amount (1000GBP -> 910GBP).

<figure><img src="/files/qIL52ewbw6SPjZzDTXYk" alt=""><figcaption><p>Wallet after validation</p></figcaption></figure>


# Developer Controls (Client-Side Integration)

This guide is for developers building a **client/front-end** (custom web app, mobile app, or a modified HollaEx web UI) on top of the **Fiat Controls** that an operator configures in **Operator Controls → Fiat Controls**.

It explains how your client reads the operator's fiat configuration and how it drives the two user-facing flows described in the Fiat Controls overview:

* **Depositing (on-ramp):** the user sees the exchange's bank/payment details, transfers funds out of band, and submits a deposit request with a payment reference. The operator later verifies it, and the fiat credits are *minted*.
* **Withdrawing (off-ramp):** the user saves their own bank/payment details, then requests a withdrawal against one of those saved accounts. The operator transfers the funds, and the fiat credits are *burned*.

> The actual movement of money happens out of band (bank to bank) and final approval is done by the operator. Your client's job is only to **read the configuration**, **collect the right inputs**, and **submit the request** — never to move funds itself.

This guide is **client-side only**. It does not cover operator/admin configuration APIs — use the in-app **Operator Controls → Fiat Controls** screens for that. All endpoints below are user-scoped and authenticated with the **user's** bearer token (never an admin/API key in a client app).

***

### 1. The three things the operator configures

In **Operator Controls → Fiat Controls,** the operator defines three pieces of configuration. Your client reads all three from the public kit config and uses them to render the UI.

| Operator tab         | Config key      | What it is                                                                                                                                                                                      |
| -------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Payment Accounts** | `user_payments` | The **catalog of payment method types** and their fields (e.g. *Bank* with `bank_name`/`iban`, *PayPal* with an email field, or a fully *Custom* type). Shared schema referenced by both ramps. |
| **On-Ramp**          | `onramp`        | Per fiat asset, **how users deposit** — the bank/payment details shown to the user, or a payment-processor plugin.                                                                              |
| **Off-Ramp**         | `offramp`       | Per fiat asset, **which saved payment-account types are accepted** for withdrawals.                                                                                                             |

The whole feature is gated behind the `ultimate_fiat` feature flag. If it is off, hide all fiat on-ramp/off-ramp UI.

***

### 2. Reading the configuration

Fetch the public kit config — it contains everything your client needs:

```js
// No auth required for the public kit config
const res = await fetch('https://<your-exchange-api>/v2/kit');
const kit = await res.json();

const {
  onramp,        // deposit config, keyed by fiat currency
  offramp,       // withdrawal config, keyed by fiat currency
  user_payments, // payment-type catalog
  fiat_fees,     // operator fee overrides, keyed by currency
  features,      // features.ultimate_fiat gates the whole feature
  coins,         // per-asset metadata: type, min, max, deposit_fees, withdrawal_fees, ...
} = kit;

const fiatEnabled = !!features?.ultimate_fiat;
```

In the HollaEx web app these are already in the Redux store as `state.app.onramp`, `state.app.offramp`, `state.app.user_payments`, `state.app.constants.fiat_fees`, `state.app.constants.features.ultimate_fiat`, and `state.app.coins`.

***

### 3. Configuration data shapes

#### 3.1 `user_payments` — the payment-type catalog

A dictionary keyed by payment-type name. Each entry lists the fields that make up an account of that type. These mirror the operator's **Payment Accounts** setup (Bank, PayPal, Custom + any "Add more payment details" custom fields with a **Field name** and a **Required** toggle).

```jsonc
{
  "bank_transfer": {
    "orderBy": 0,
    "data": [
      { "key": "bank_name", "label": "Bank name", "required": true },
      { "key": "iban",      "label": "IBAN",      "required": true }
    ]
  },
  "paypal": {
    "orderBy": 1,
    "data": [ { "key": "email", "label": "PayPal email", "required": true } ]
  }
}
```

* The **key** (`bank_transfer`) is the canonical payment-type id referenced by `offramp`.
* `data` is the ordered list of field definitions; each has at least a `key` (use it as the form field name) and typically `label` and `required`.
* To render them in order, flatten to a sorted list:

```js
const paymentTypes = Object.entries(user_payments)
  .map(([name, def]) => ({ name, ...def }))
  .sort((a, b) => (a.orderBy ?? 0) - (b.orderBy ?? 0));
```

#### 3.2 `onramp` — deposit configuration

Nested **currency → method name → method definition**:

```jsonc
{
  "usd": {
    "bank_transfer": {
      "type": "manual",
      "data": [ /* rows of bank-detail fields to display to the user */ ]
    },
    "stripe": {
      "type": "plugin",
      "data": "stripe"          // plugin / processor identifier
    }
  },
  "gbp": { /* ... */ }
}
```

* `type: "manual"` — show the user the exchange's deposit instructions (the detail rows in `data`), collect an amount + a payment reference, then submit a deposit request.
* `type: "plugin"` — a third-party processor drives the flow; `data` is the plugin id you hand off to. (In the HollaEx web app this renders a `SmartTarget` with id `generateDynamicTarget(data, 'ultimate_fiat', 'onramp')`.)

#### 3.3 `offramp` — withdrawal configuration

**currency → array of accepted payment-type keys** (keys reference `user_payments`):

```jsonc
{
  "usd": ["bank_transfer", "paypal"],
  "gbp": ["bank_transfer"]
}
```

Meaning: "USD withdrawals are accepted via a `bank_transfer` or `paypal` account." Resolve each key against `user_payments[key].data` to know which fields the account needs.

#### 3.4 The user's saved payment accounts (`user.bank_account`)

A user's concrete withdrawal accounts are stored as an array on their profile. Each entry:

```jsonc
{
  "id": "a1b2c3d4",          // unique id — used as `bank_id` when withdrawing
  "type": "bank_transfer",   // should match a user_payments key
  "status": 3,               // 0 = pending, 3 = verified
  "bank_name": "Example Bank",
  "iban": "DE89..."
  // ...the fields defined by the payment type
}
```

* **Only verified accounts (`status === 3`) can be used to withdraw.**
* Read it from the authenticated user object (`GET /user`), available as `state.user.userData.bank_account` (or `state.user.bank_account`) in the web app.

***

### 4. Client endpoints

All endpoints are authenticated with the **user's bearer token**:

```js
const authHeaders = { Authorization: `Bearer ${userToken}`, 'Content-Type': 'application/json' };
```

#### 4.1 Submit a deposit (on-ramp)

`POST /fiat/deposit`

```js
await fetch('https://<api>/v2/fiat/deposit', {
  method: 'POST',
  headers: authHeaders,
  body: JSON.stringify({
    amount: 100.0,            // required (number)
    transaction_id: 'REF123', // required — the user's bank/payment reference
    currency: 'usd',          // required
    address: 'bank_transfer'  // optional — the on-ramp method key the user chose
  }),
});
```

The response is a **pending** deposit awaiting operator verification. Reflect that "pending" state in your UI — the credits are not available until the operator approves it.

Server-side rules to mirror as client-side pre-checks:

* The user must be verified (`verification_level >= 1`).
* The user may have at most **3 pending deposits** for a currency at once.
* `amount` must be within the asset min/max and the user's deposit limit.

#### 4.2 Submit a withdrawal (off-ramp)

`POST /fiat/withdrawal`

```js
await fetch('https://<api>/v2/fiat/withdrawal', {
  method: 'POST',
  headers: authHeaders,
  body: JSON.stringify({
    amount: 50.0,        // required (number)
    bank_id: 'a1b2c3d4', // required — id of a VERIFIED entry in user.bank_account
    currency: 'usd'      // required
  }),
});
```

The response is a **pending** withdrawal (burn) awaiting operator processing.

Server-side rules to mirror:

* `bank_id` must match one of the user's saved accounts, else *"The selected payment option is not registered."*
* The user must be verified, not a sub-account, and within withdrawal limits.
* At most **3 pending withdrawals** per currency at a time.
* Balance must cover `amount + fee`.

#### 4.3 Manage the user's payment methods (`/user/payment-details`)

Use these to let users create and manage their fiat-control payment accounts. Flag fiat-control records with `is_fiat_control: true`.

| Method   | Path                    | Body / Query                                                                                                 |
| -------- | ----------------------- | ------------------------------------------------------------------------------------------------------------ |
| `GET`    | `/user/payment-details` | query: `is_fiat_control`, `is_p2p`, `status`, `limit`, `page`, `order_by`, `order`, `start_date`, `end_date` |
| `POST`   | `/user/payment-details` | `{ name, label?, details, is_p2p?, is_fiat_control?, status? }`                                              |
| `PUT`    | `/user/payment-details` | `{ id, name?, label?, details?, is_p2p?, is_fiat_control? }`                                                 |
| `DELETE` | `/user/payment-details` | `{ id }`                                                                                                     |

```js
// Create a payment method
await fetch('https://<api>/v2/user/payment-details', {
  method: 'POST',
  headers: authHeaders,
  body: JSON.stringify({
    name: 'My EUR bank',
    is_fiat_control: true,
    details: { bank_name: 'Example Bank', iban: 'DE89...' }, // keys from user_payments
  }),
});
```

> A user **cannot** set or change `status`, and **cannot** edit a record once it has been verified (`status === 3`). Verification (raising status to 3) is done by the operator. Newly created methods start unverified and become usable for withdrawals only after the operator verifies them.

#### 4.4 Fees & limits (helpers)

* **Fee:** read from `coins[currency].deposit_fees` / `coins[currency].withdrawal_fees` (falling back to `coins[currency].deposit_fee` / `withdrawal_fee`). Prefer the operator override `fiat_fees[currency].deposit_fee` / `.withdrawal_fee` when present.
* **Limits:** resolve from `transaction_limits` by matching `limit_currency` (the currency, else `default`), the user's `verification_level`, and `type` (`deposit` / `withdrawal`).
* **Min/max amount:** `coins[currency].min` / `coins[currency].max`.

(The HollaEx web app wraps these as `getFiatDepositFee`, `getFiatWithdrawalFee`, `getFiatDepositLimit`, `getFiatWithdrawalLimit`.)

***

### 5. Building the deposit (on-ramp) UI

1. **Gate:** require `features.ultimate_fiat` and `coins[currency].type === 'fiat'`.
2. **Require verification:** if the user is not verified (`verification_level < 1`), prompt them to

   complete verification before depositing.
3. **Read** `onramp[currency]`. If empty/undefined, show an empty state (no deposit method

   configured).
4. **Render one tab per method** — iterate `Object.entries(onramp[currency])` → `[methodKey, { type, data }]`:
   * `type === 'manual'`: show the deposit instructions from `data`, plus the min/max/fee summary, then collect `amount` and `transaction_id`.
   * `type === 'plugin'`: hand off to the processor identified by `data`.
5. **Submit:** `POST /fiat/deposit` with `{ amount, transaction_id, currency, address: methodKey }`,

   then show the resulting pending state.

```js
const methods = onramp?.[currency] || {};
Object.entries(methods).forEach(([methodKey, { type, data }]) => {
  // render a tab; for "manual" show `data` rows, for "plugin" mount the `data` processor
});
```

Reference implementation: `web/src/containers/Deposit/Fiat/`.

***

### 6. Building the withdrawal (off-ramp) UI

1. **Gate:** same `ultimate_fiat` / fiat-currency / verification checks.
2. **Read** `offramp[currency]` (array of accepted payment-type keys). If empty, no withdrawal

   method is configured.
3. **List the user's usable accounts:** take the user's **verified** accounts

   (`user.bank_account.filter(a => a.status === 3)`) and keep those whose `type` is in `offramp[currency]`. If the user has no verified account of an accepted type, prompt them to add one (Section 4.3) and have it verified.
4. **Collect input:** let the user pick an account and enter an `amount`; show the fee and limit and

   ensure `amount + fee <= balance`.
5. **Submit:** `POST /fiat/withdrawal` with `{ amount, bank_id: account.id, currency }`, then show

   the pending state.

```js
const acceptedTypes = offramp?.[currency] || [];
const usableAccounts = (user.bank_account || [])
  .filter((a) => a.status === 3 && acceptedTypes.includes(a.type));
```

Reference implementation: `web/src/containers/Withdraw/Fiat/` and `web/src/containers/Wallet/AddressBook.js`.

***

### 7. Quick reference

| You need to…                                  | Do this                                                                     |
| --------------------------------------------- | --------------------------------------------------------------------------- |
| Detect if fiat is enabled                     | `GET /kit` → `features.ultimate_fiat`                                       |
| Read deposit / withdrawal config              | `GET /kit` → `onramp[currency]` / `offramp[currency]`                       |
| Read payment-type fields                      | `GET /kit` → `user_payments[type].data`                                     |
| Read the user's saved accounts                | `GET /user` → `bank_account` (verified = `status === 3`)                    |
| Submit a fiat deposit                         | `POST /fiat/deposit` `{ amount, transaction_id, currency, address }`        |
| Submit a fiat withdrawal                      | `POST /fiat/withdrawal` `{ amount, bank_id, currency }`                     |
| List / create / edit / delete payment methods | `GET / POST / PUT / DELETE /user/payment-details` (`is_fiat_control: true`) |
| Show fees / limits                            | `coins[currency]` + `fiat_fees[currency]` + `transaction_limits`            |

#### Reference front-end source

| Area                          | File                                                                                    |
| ----------------------------- | --------------------------------------------------------------------------------------- |
| Reading config into the store | `web/src/actions/appActions.js` (`setConfig`)                                           |
| Deposit (on-ramp) UI          | `web/src/containers/Deposit/Fiat/`                                                      |
| Withdrawal (off-ramp) UI      | `web/src/containers/Withdraw/Fiat/`                                                     |
| Saved payment accounts UI     | `web/src/containers/Wallet/AddressBook.js`                                              |
| Fee / limit helpers           | `web/src/containers/Deposit/Fiat/utils.js`, `web/src/containers/Withdraw/Fiat/utils.js` |


# Staking

CeFi Staking is a great way to get your users more invested (literally) in your exchange and reward them for keeping assets on your exchange

## How HollaEx Staking Works

HollaEx Exchanges utilize a CeFi method of staking. This differs from the blockchain-based DeFi method in a few ways.

HollaEx CeFi staking avoids utilizing the blockchain and instead, the software calculates and distributes rewards depending on your defined parameters.&#x20;

The way this works in a nutshell:

* The operator sets up the Staking Pool, choosing what asset will be staked, what the reward will be, and the parameters such as duration and penalties for early unstaking.
  * Here, the operator also chooses a funding source. This is simply an account where rewards will be taken from&#x20;
* A user chooses to participate in this pool, staking their assets, essentially 'locking' them to the exchange.&#x20;
  * If the user unstakes early, penalties such as forfeiting a percentage of their initial stake are applied, which are then transferred to the funding source account mentioned above.
  * If the user holds their stake until the end of the set duration, they receive back their initial asset, as well as the calculated amount of the reward asset, paid out from the funding source

{% hint style="warning" %}
Due to this CeFi method of staking that HollaEx utilizes, it is vital that the funding source account has sufficient assets stored on it at the start of the pool, and is kept maintained through the lifetime of the pool.

This funding source account lacks assets; the rewards will not be paid out to users, even if they complete a stake.
{% endhint %}

***

## How to Set Up Staking

To start a Staking Pool on your exchange, navigate to the Operator Controls and find '*Stakes'* on the Sidebar. Ensure that the '*Allow CeFi Staking*' toggle is set on, and follow up by clicking the green '*Create CeFi Stake Pool*'.

<figure><img src="/files/ZceIdg2ZKVDcU1pNwugK" alt=""><figcaption><p>Creating a new staking pool from the Operator controls</p></figcaption></figure>

### Create a Stake Pool

#### Selecting Assets

This will open a menu where the *Asset for staking* can be chosen from active assets on your exchange. Once this is chosen, the menu will expand, and the *Asset for rewarding* can be chosen.&#x20;

{% hint style="info" %}
In most cases, these will be the same (eg, Staking USDT will reward back USDT), but there is an inbuilt mechanism that will convert one asset to the equivalent worth of another (eg, Staking BTC returns USDT).
{% endhint %}

<figure><img src="/files/t7qcKALDk3zTyH4XYOKz" alt=""><figcaption><p>Distrubution Rate Settings</p></figcaption></figure>

#### Name

Following this, choose a name for the pool. This will be shown to users.

<figure><img src="/files/0tIdWgsBupA5EmhYcgis" alt=""><figcaption><p>Pool Name Input</p></figcaption></figure>

### Distribution Rate Settings

#### Annual Percent Return (APY) & Staking Duration

This defines what the APY (Annual Percent Return) of the staked assets will be. Alongside this, the *Staking Duration* is chosen. This defines the amount of time that the assets must be locked into the exchange, for the reward to be paid out (and penalties to be avoided).

<details>

<summary>Example of Distribution Rate Settings</summary>

Formula: $$return = (initialstake \* APY \* 0.01 \* duration) /  365$$

Example settings: APY = 3%, Duration = 31 days

In this scenario, a user who staked 1000USDT would, after the 31-day staking period, receive back:

$$return = (1000\* 3\* 0.01 \* 31) /  365 = 2.54$$

And so after this staking period would have 1002.54 accessible in their HollaEx wallet (with the 2.54 USDT, being transferred out of the source funding accounts USDT).&#x20;

</details>

<figure><img src="/files/Iu43l1ocgLw7fmpVUJHj" alt=""><figcaption><p>Distrubution Rate Settings</p></figcaption></figure>

#### Perpetual Staking

Included in the above menu is the option for *'Perpetual Staking'.* This means that there is no minimum staking duration, and users can choose to unstake the assets at any time, earning the equivalent return from how many days they left staked, essentially allowing users to decide the Staking duration.

{% hint style="info" %}
This of course, means that due to there being no minimum, there are no penalties to users for unstaking and thus they will only ever gain assets from the staking process.
{% endhint %}

***

### Unstaking

Unstaking simply refers to a user deciding that they want access to their staked assets and removing them from the pool. This can incur a penalty where a percentage of their assets may be transferred to the exchange itself, to encourage users to keep their stake for the duration.

#### Unstake Early

This defines if a user can choose to unstake. If no is selected the user **cannot** access their staked assets at all, until the staking period is complete.

{% hint style="warning" %}
The no selection for unstaking should be used only if considered thoughtfully, as some users may be frustrated by the inability to retrieve assets, even knowing penalties may apply.
{% endhint %}

#### Slashing Rules - Principle & Earnings

Slashing is the term used to describe the penalty applied to a user's staked amount of assets. This penalty can be applied in two ways:

**Slash on Principle** - This is the percentage taken from the total amount of the initial staked asset. For example, if this is set to 5%, a user unstaking earlier, who staked 1000USDT, would receive back 950USDT, and the source wallet that is chosen would receive the 50USDT.

**Slash on Earnings** - This refers to the percentage taken of the earnings that a user has added to their stake until that point. For example, if a user had earned 5USDT up to a point, and had chosen unstake earlier than the full staking duration, if the Slash on Earnings was set to 50%, they would receive 2.5USDT (plus their initial stake minus any Slash on Principle %), and the pools source wallet would receive the other 2.5USDT

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

***

### Parameters

This menu is simply the minimum and maximum amount of assets a user can apply to the pool at any one time.

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

### Disclaimer

This is simply a message that will be displayed to users when they are going to stake. This could be used to alert them to the terms you have set, and ensure they know and understand the process.

### Funding Source

This allows you to choose an account on the exchange where the rewards that your users receive for successful staking will be sent. This means that for a smooth process and happy users, this source wallet is continually ensured to have a sufficient balance of the reward asset&#x20;

<figure><img src="/files/PoEGvPVX21sln7owhndw" alt=""><figcaption><p>This account above, would not be a great choice, due to having little of the rewarding assets stocked</p></figcaption></figure>

{% hint style="warning" %}
It is critical to the smooth running of the pool that this funding source account has sufficient amounts of assets for the expected amount of rewards to be paid out.
{% endhint %}

***

### Review & Open Pool

After this there will be a review page, to take a chance to ensure all previously chosen settings are correct.

Following this review page will be the Open pool menu this again gives a chance to ensure all settings are correct before finalizing the pool.  To confirm and proceed, type 'I UNDERSTAND' in the textbox and click next.

{% hint style="info" %}
Once the pool is finally set up, initialized, and open to the public, it is recommended not to edit the pool further, as this may have unexpected consequences with user funds.
{% endhint %}

## Opening the Pool to Your Users

With the settings of the pool chosen, there are a few minor selections to get it launched and available to users. On the *Stakes* page now, a new record will be visible with the details and most importantly several settings on the right side.

<figure><img src="/files/JvxDJuCbmjCq51nNV2vr" alt=""><figcaption><p>Full Record of the new pool</p></figcaption></figure>

Taking a closer look we see *Onboarding* and *Status* Fields.

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

First, we must edit the *Status* field, and initiate the pool. Simply hit *Edit*, and select the option for *Open the Pool*.

After this is done, the pool is ready to go and *Active*, but to actually allow users to begin staking we need to open the Onboarding process in the same way, hitting *Edit* under *Onboarding*, selecting the *Open Onboarding* radio button, and hitting *Next*.

## Using the Staking Pool

Finally, users will be able to see and interact with the new Staking Pool. Heading to the Stake page and switching the toggle button from DefI to Cefi, the newly set up pool will be visible, with the important details shown to users.

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

As the user goes through the process of the staking, they will be informed of the slashing rules, and given a message explaining what Slashing entails.

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

Finally, the user can review the details of their stake, as well as be shown any disclaimer message that was included in the initial setup.

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

After a final confirmation message the user will have the assets staked, and hopefully remain patient enough to receive their rewards!


# OTC Broker

The OTC broker allows users to buy and sell assets at fixed rates via the quick trade tab, prioritizing convenience, and speed of use, without removing the option for users of using the Orderbook.

{% hint style="info" %}
📺 Watch on YouTube!:[ ](https://www.youtube.com/watch?v=jT-zegaUO6U)

1. [Creating an OTC Broker Deal (Static Pricing)](https://www.youtube.com/watch?v=jT-zegaUO6U\&ab_channel=HollaEx)
2. [Dynamic Pricing OTC Broker](https://www.youtube.com/watch?v=aZIX5epOMbc\&ab_channel=HollaEx)
   {% endhint %}

## Setting up an OTC Broker Deal

{% hint style="info" %}
Due to the nature of how the OTC Broker sources bought funds, an account on your exchange must have a sufficient balance for both of the assets that constitute the trading pair.
{% endhint %}

Setting up a deal with an OTC Broker is almost as easy as a user trading with one. From an admin account on your exchange, enter the Operator controls, and find the OTC Broker tab.

![Starting an OTC Broker deal](/files/JTN81fXW0Yg5NyuBurs2)

From here, the ‘*Start new deal’* button will open the menu to configure the deal.

![Choosing tading pair](/files/TvCVsQTM3tKPzd7kteNo)

First, define the base asset (what will be traded) and what it will be priced against (what the trading pair will be).&#x20;

Your trades will be able to go both ways; the importance here of order is when setting the price ratios between the two currencies.

![For example, with the above settings, users can only trade in whole numbers of HollaEx Tokens, to a maximum of 1000. ](/files/pOYb7IHaH766c5z8o9O6)

Now define the parameters defining minimum/ maximum amounts, as well as the increment amount

* *Minimum/ Maximum Tradable Amount*: The minimum/ maximum amount of the base asset that can be traded on this deal.&#x20;
* *Increment Amount*: The lowest decimal value increase/decrease possible.

## Asset Pricing Method

There are two options for how the base asset is priced in terms of the pricing asset, chosen from the dropdown menu.&#x20;

* **Static Pricing** - The base asset is set to a particular amount for both buying and selling. This will not change until the operator chooses to change.
* **Dynamic Pricing** - The system allows the setup of formulations to create prices, including retrieving prices periodically from other exchanges. &#x20;

In both cases, here selling refers to the price **the exchange is selling to the user at**, and the buying price is the price **the exchange will buy** **from a user**.

Pricing is discussed in more detail on the following page:

{% content-ref url="/pages/jmNweEQlS8OShaEX2mKf" %}
[OTC Broker Pricing](/how-tos/otc-broker/otc-broker-pricing)
{% endcontent-ref %}

### Static Pricing

Static pricing is simple to set up. Choose the selling and buying prices of the base asset relative to the pricing asset. This price will not change until the operator edits it. Once these prices are chosen, hit next and continue to the '[Source Wallet](#source-wallet)' section.&#x20;

![Setting static pricing of XHT-USDT ](/files/QVzOc7yfNqK0wpAPcFdb)

### Dynamic Pricing

Dynamic pricing has a couple more options than static pricing:

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

* *Formula* - This is how the base price of the primary asset will be priced (in the pricing asset). By default, the system will retrieve the Binance price of the chosen market. This can be custom set; see the following page for details on this.
* *Percentage Price Spread* - This value will edit the formula price value of the asset in question, and add/ subtract this percentage to buy/ sell orders, respectively.&#x20;
* *Price Refresh Interval* - This is how often the exchange will seek out the value from the chosen outside exchange (Binance, Kraken, etc.), run the defined formulation (see Advanced below), and set the price that will be shown to users on the Quick Trade screen.
* *Price Quote Expiry* - This is simply the time in seconds between a user typing in a value to the Quick Trade page for the OTC deal and receiving an initial quote, and being told the initial quote is no longer valid and has to generate a new one.
* *Result* - The real-time price (based on the formula) can be generated and checked here by clicking the 'Show price result' link.
* *Advanced* - Here, the formula for the base price is defined. There is a lot of flexibility in creating exactly the equation wanted using general mathematical operators, as well as retrieving prices from outside exchanges. The form of these price retrievals is such:
  * <*Exchange\_Name*><*Market*> -> e.g. *'kraken\_eth\_usdt'*

Here, selling refers to the price **the exchange is selling at**, and the buying price is the price **the exchange will buy** **at**.

## Source Wallet

The next choice is what source the funds the brokerage will draw from. The options presented in the dropdown are taken from the exchange's accounts.

![Choosing source account, note the balance displayed of bot the chosen assets](/files/Xt8L2SVoLfI9VihwaSJA)

Primarily, you, as the exchange owner, will want to use either the admin account or a dedicated source account to provide the initial liquidity required.&#x20;

{% hint style="warning" %}
Ensure this account has access to sufficient funds for both assets of the pair, as otherwise, trades will not be possible if users want to buy/ sell amounts larger than the source wallet's available assets
{% endhint %}

## Select Status & Enable OTC Trade

The next option is the status of the OTC deal. If an OTC deal is set to live, then we can move on to the tab next to the OTC Broker's 'Quick Trade'. From here, find the newly created OTC pair, click the gear icon, and choose OTC to instantly enable the market.

***

## Configuring an Existing Deal

Fortunately, changing how your OTC deal behaves is a simple task by using the configure button to the right of the deal in your listing. This will allow you to go through the steps detailed above once more.

The deal can also be paused or unpaused, without deleting it, through the ‘edit’ link, in the ‘State’ field of the deal, on the OTC Broker page.

![](/files/OefbjQ0rWjWCG68envab)

## Deleting an OTC Deal

If, instead, you want to fully remove a deal from the exchange and revert to using the orderbook between the pair, using the edit link within the ‘State’ field of the deal, there will be a remove link at the bottom of the pop-up menu.

![](/files/tBBlQVPA6HeBHepsdOZY)

***

## Using the OTC Broker

The OTC broker offers possibly the simplest method of trading on your exchange. The user desiring the trade will be given a simplified screen when compared to the orderbook, or the quick trade using the orderbook.

![](/files/p2VlByJdJiTyRg9iopct)

User flow:&#x20;

1. On the Quick Trade screen, choose the pair desired to trade within the Quick Trade dropdowns.
2. See the amount of the desired currency, calculated from the static pricing the exchange operator has set
3. If the balance of the source wallet is sufficient to facilitate the trade, the trade will instantly take place. If the source wallet doesn't have the funds, then the trade will be rejected


# OTC Broker Pricing

The OTC Broker allows complete control over the specific price assets are valued at, and can be made to change automatically to match current market values with no oversight needed

## Pricing OTC Broker Assets

When setting the pricing of any created OTC market, there are two primary options available:

* **Static** - Simplest option, where the direct pricing of an asset, with respect to the matching other asset, is set at a certain value and will not change. This may be of use when creating a market for a token whose market value is not expected to change, for instance, where you, as the exchange operators, are issuing a new token under your control and want full control over what it is traded at.
* **Dynamic** - This pricing method allows for the given market to track the market value obtained from an outside source, and will regularly update the pricing to ensure you are offering the market at a suitable price to ensure you remain both competitive and profitable.

## Dynamic Pricing

When using Dynamic Pricing, by default, the exchange will set the fetched price from Binance (if the market exists). By using the advanced controls, however, we can change this source and work with that source price (or prices) however we desire.

To illustrate this, let's consider the examples of creating a BTC/ETH OTC market.

#### Dynamic Pricing BTC/ETH Example

Setting up the OTC market as described on the OTC Broker page, selecting Dynamic Pricing will open the menus that allow us to set the pricing. First, select where we'll take the pricing source from, and then, with this chosen, find the relevant market using the Track Market Price input, searching the market - if your desired market is not available, see this section on how to deal with this scenario.&#x20;

Next, choose the price spread, the percentage value that will be applied to the base price to ensure profit per trade.&#x20;

The price quote expiry time is simply the time (in seconds) after which the displayed price to the user will expire and need to be refreshed.

With these set, we can click Show price result to check what the values of buy and sell will be.

Now, we can either advance to the next menus or take a look at the advanced sections.&#x20;

#### Advanced Price Setting

It is possible to further define the pricing to exactly what is required using the Advanced link at the bottom of the pricing menu.&#x20;

Opening this menu, we can use arithmetical operators to further formulate exactly how the retrieved pricing is transformed.

For a simple example, with the formula, `(binance_eth-btc+coinbase_eth-btc+kraken_eth-btc)/3` we can obtain the average price of the three major exchanges. This is course can be made as complex as required.

#### Even More Advanced Price Setting

Through the use of simple JSON code, we can go further than the built-in price sourcing. By using the APIs of sites like Coin Gecko, it's possible to utilise values taken from these sites, which can give access to more niche market data.

As in the above ETH/BTC example, instead of taking the value from the major markets that Dynamic pricing supports, we could instead use Coin Gecko with the following JSON snippet:

```
{

 "request": {

   "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eth"

 },

 "extract": {

   "type": "jsonpath",

   "expr": "$.bitcoin.eth"

 },

 "normalize": {

   "decimalPlaces": 2

 }

}
```

This JSON can be customised to whatever is required to get you exactly whatever it is you and your users need.


# P2P

The HollaEx P2P allows users on your exchange a simple way to directly trade crypto & fiat with each other, without needing you as the admin to directly assist in the process.

## What is the HollaEx P2P System?

The HollaEx P2P system allows users to buy and sell selected crypto assets directly from each other in exchange for fiat currencies.<br>

This enables on-ramping between users directly, rather than you as the operator needing to provide fiat liquidity and on-ramping methods.

***

## Glossary

**P2P** - Peer-to-peer (in the crypto world) refers to a system in which two parties (the vendor and the buyer) can transact crypto/ fiat assets, without the need for a middleman or third party.

**Vendor (Makers)** - The party that creates the P2P deal and sets the price. This is a (potentially non-exchange affiliated) user on the exchange that uses their crypto assets and offers them&#x20;

**Customer (Takers)** - Here refers to the other side of the transaction, to the vendor.

**Operator/ Admin** - These terms are generally interchangeable throughout the docs, and refer to you as the owner of the exchange, doing  the initial setup of the P2P system

**Payment Method** - Fiat payment methods such as bank deposit info or PayPa,l and other fintech provider payment details.

**Minimum Tier** - The user account's minimum tier level needed to participate in the P2P market.<br>


# P2P Overview

First, a brief look at the user-facing pages of the P2P system, to get an idea of what opportunities it enables.

## Exchange P2P Pages

For an initial bird’s-eye view of the P2P system, here is a quick explanation of each page that can be encountered by users when they navigate to the P2P system (found linked on the sidebar under P2P).

### P2P

The initial tab shows all available posted P2P deals. Think of this as the central marketplace, where prospective buyers can browse through all deals posted by vendors, see the details of both the deal and the vendor, and then buy or sell.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdDCsnsuA2VsQPMkrQAeHI2bhwPH77jJcK74-HpdJp0weehigD8wu-7-CwW2Pge4HHPm2ZI5tO9xHyEPk0euEKLlAP37YpTzV26sP-1eKfk-liv5dgDrhJd9SHG5Xr3-_FPA9yUSw?key=Wat7u4yLwhAVbduuRiUzSgkM" alt=""><figcaption><p>P2P Page</p></figcaption></figure>

### Orders

This page displays all the orders that a vendor or user has active (meaning they have not been completed) and allows vendors to confirm transactions, as well as view previously occurred deals and their details.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdosLPbPUuksD3wkKh3TF2KOQKSp1dWgVzspdjFOQ8BuFLP1I5Xn4uKFGQBZvoZEA2eNUrWvE1DasRLxbYSZ3RrDu_wBVHs0vHk1IyKnLnnAeyvyykdbU_WjmMgQ8M3rx2UNzWa?key=Wat7u4yLwhAVbduuRiUzSgkM" alt=""><figcaption><p>P2P Orders</p></figcaption></figure>

### Profile

Displays the profile of the logged-in user. This page allows the user to add their payment methods (bank deposit info, PayPal, etc.), as well as view the feedback from users they have previously transacted with.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfz1VERIga3_6BhpYiGwF8JqmFD6wJaGr4g9XAks8hCGFhHBvLBzkAKYKZBRjHkV7AHLmftryXBZ1Ats2QWK5Axslpt5hF563Q0opiTfPVHY0RwEGamMW5pFpKy4hPpxNQGrF1A?key=Wat7u4yLwhAVbduuRiUzSgkM" alt=""><figcaption><p>P2P Profile</p></figcaption></figure>

### Post Deals

This tab allows the creation of new deals, setting up the desired configurations of whatever fiat/ crypto pairing is chosen by the vendor.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXc5OZ5bopPakmAZFapQw4tOFH83JUXA1PIH2HCc3D2lKP5GYBirVIUQR754KHLB3xpMVh0ktqKZDAHTQs6YVDQBJBuwnlYRMM05p8Ow46Qo9HrLFuKHiNEHbdok1ya-wfikDd9e?key=Wat7u4yLwhAVbduuRiUzSgkM" alt=""><figcaption></figcaption></figure>

### My Deals

Here, all deals that have been created by the vendor in the ‘Post Deal’ tab are visible and able to be edited, paused, or deleted.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcEXavmZAg49UrrHTbknzlNtItn3DFzoazBuy2gqLpLrXWnS87sXKXvQu3s5Tfxp79nByOwm4aJZepUHX3MArt6fVDB10bJhvP-Cz_HMkYhDTwrUDeT6m9Xdd7NFxQ-bx3DvjmK?key=Wat7u4yLwhAVbduuRiUzSgkM" alt=""><figcaption></figcaption></figure>

<br>


# P2P Setup

By default, on the setup of an Enterprise exchange, the P2P feature will be visible to users - assuming your exchange is running on version v2.11 or later.&#x20;

If it is not visible (and you have updated to a version post v.2.11), head to Operator Controls -> General -> Features, and ensure that the check box beside ‘P2P’ is enabled, as shown in the image below

{% hint style="info" %}
Keep in mind that P2P is only available (and thus visible in the Features tab) on Enterprise plans
{% endhint %}

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfuTcaa40Utj-YaLb4kDYg_JVIYpoO8-agMQfcjhgbxOSWFyoC4i4sV-28nVpSE-b7GIzV2FMioWHyslQdVjXq2iZ7bmdePbbEY76Y2ABJG7L34RbFxQr9S-ToKjm6M5SVOsE2rnw?key=Wat7u4yLwhAVbduuRiUzSgkM" alt=""><figcaption><p>Enabling P2P feature</p></figcaption></figure>

***

With this done, it is likely at this point though that some tweaking may be required to ensure that users can take advantage of this feature.&#x20;

To get the foundations laid down, head to the *Operator Controls -> Markets -> P2P -> P2P Settings*

From here, we need to ensure the *Enable* toggle on the right side of the screen is enabled, and then click the green *Edit Settings* button to create our initial configurations of how we want vendors to be able to create deals.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcwFiXQguPCkNgxAeTRL_JfGeaPJe7kamKzh3jBW8JcdFf4UGy3s7xMn3g_3nGeQOgwixAu4rKjko4s1vcC6_-UKTw1HxcChMEzzYsadMqxDicKk_9KKuXsWJQ2xWwh5TX9f4yqxQ?key=Wat7u4yLwhAVbduuRiUzSgkM" alt=""><figcaption><p>Editing P2P Settings</p></figcaption></figure>

## Edit Settings Menus

By opening the settings menu we will be able to select the options we want to offer to our users and vendors.<br>

1. The first screen lets us choose the baseline configurations:
   1. The allowed directions of trades (sides) that vendors can post. This can be limited to just allowing buy or sell, or enabling both types of transactions.&#x20;
   2. Here we can also choose all the assets (both crypto and fiat) that can be used in vendor deals.
      1. As well here we can select
      2. For information about adding assets to the exchange as a whole, check out these pages for crypto assets, and fiat assets
      3. **Note**: The fiat currencies that are selectable here are not the ramp options. Leave these unselected as we will select them on the next page
   3. The transaction duration refers to the length until a deal expires.

<figure><img src="/files/m3sRsktXSVPpCPsPRlAt" alt=""><figcaption><p>P2P Settings menu</p></figcaption></figure>

2. Next up we can select the fiat assets that can be used to trade for crypto. See the previous point for adding these types of assets.

<figure><img src="/files/ozOqDt7VIiMCAInjBx2J" alt=""><figcaption><p>P2P Fiat Choice</p></figcaption></figure>

3. This step is an important one, as it defines exactly who will be able to use the P2P system, and what exactly they will be allowed to do. Please see these pages on the User Tier to get the details on this system.
   1. This step is an important point of consideration. Vendors must be trusted users on your exchange, due to them dealing with the assets of your other users, and thus will refelct on your exchange. However you choose to perform checks on these users, ensure that this setting is limited such that you know you can be confident in thier ability to interact with the rest of your user base.
   2. Of course, the same rule goes for your users, that take the deal of the above vendors, but perhaps require a lesser level of tier

<figure><img src="/files/b7LUKvoP5n8PGUtmAghY" alt=""><figcaption><p>P2P User Requirements </p></figcaption></figure>

4. Next up, we select the payment methods with which vendors and users will use to send and receive fiat.
   1. A long list of various methods are provided by HollaEx, but this can be expanded by you for exactly the payment services that work best for you and your userbase
   2. For information on adding new payment methods, check out this page in the Fiat Controls

<figure><img src="/files/exEiifktVXMNYyA3Vy8s" alt=""><figcaption><p>P2P Payment Methods</p></figcaption></figure>

5. Our fifth menu gives options about the fees that vendors and customers will incur using the P2P system.&#x20;
   1. The first two input boxes are the percentage fees for vendors and customers
   2. The dropdown menu defines the exchange wallet (generally the [admin's fee settlement](https://docs.hollaex.com/how-tos/operator-control-panel/assets#earnings) wallet) where these accumulated fees will be sent.

<figure><img src="/files/suE0TyhePnBKe73bJv06" alt=""><figcaption><p>P2P Fees Settings</p></figcaption></figure>

6. Finally, we can review all our chosen settings, and confirm once we are happy with them.


# P2P Troubleshooting

The P2P system is simple to set up, but on occasion, there may be issues in getting it to function as you want. This page hopes to alleviate these specific issues.

{% hint style="info" %}
If your issue isn't listed or the fix has not been done as you wish, please send the team at *<support@hollaex.com>* a message, and they will be sure to assist in your case as soon as possible.
{% endhint %}

With the initial configurations complete, you may notice that you and your users cannot take advantage of the P2P system you have set up, or it behaves differently from your expectations. Depending on your configurations, some additional steps may be required to get things running smoothly.&#x20;

## P2P Settings

### Vendors can create Buy/ Sell orders, but not the others

If you find that, once the P2P system is set up, the buy/sell toggle button at the top of the screen is locked to one side, this is a simple fix.

The most likely candidate for causing this behavior is in the *Sides* option. To find this, head to the *Operator Controls* -> *Markets* -> *P2P* -> *P2P Settings* and check the first listed option *Sides*. This is shown in the image below:

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

This option defines what '*side*' of a trade a vendor can create. In essence, whether they are limited to simply selling crypto for fiat or vice versa.&#x20;

If you want to allow both sides and notice the Sides option is limited to one, simply hit the green 'Edit Settings' button in the top right, and from the menu ensure '*All*' is selected.

## Users ( Including Admin) Cannot See the '*Post Deal*' Tab

This is likely due to the tier system and tier requirements set for the P2P system. Accounts that have obtained a user tier, defined by the admin in the setup stage, can see this particular tab, but users on any tier below this will not have it visible.

The primary way to tackle this issue is to ensure that those users who you wish to act as vendors are on the necessary (or higher) account tier needed to post trades. To manually upgrade a user's tier, head to the User page on the Operator Controls sidebar, find (via the search bar) the user you desire to be made a vendor, and access their User page.&#x20;

From here, find the Tier option from the *User Info* section, where you can manually assign them the required tier to be a vendor. This option is shown in the image below:&#x20;

<figure><img src="/files/1uhyPhhJUTFR31T3Ojrz" alt=""><figcaption><p>User Tier Setting</p></figcaption></figure>


# P2P Vendor Flow

Once a user on your exchange has been given the required account tier to be a Vendor, they will be able to view the *Post Deal* tab on the P2P section of the exchange, shown in the image below:

<figure><img src="/files/8BvFZWGpH9UDQjz9iZKk" alt=""><figcaption><p>Post Deal Tab of P2P</p></figcaption></figure>

## Creating a Deal

The vendor will be presented with the options required to create their desired deal. Going through each page in turn:

### 1. Set the type and price

This first page allows the tuning of the deal itself.

First, the user will select whether they wish to buy (receive crypto from other users in return for fiat) or sell (vice versa). This is accomplished by the coloured toggle in the top-center of the options.

The two leftmost options allow the choice of crypto and fiat in the deal, in the relevant direction.&#x20;

The three stacked input boxes on the right relate to the following:&#x20;

* **Type**: This defines whether the chosen price is *Static* or *Dynamic.* This concept is described on the following OTC doc page and works the same here in P2P. In summary, in Static deals, the vendor will choose the exact price of the chosen crypto and fiat, and for Dynamic, the price will be tracked on exchanges like Binance or Kraken automatically (assuming a relevant market exists).&#x20;
* **Price**: Unsurprisingly, this is the price at which the deal will take place. For Static deals, this price will be set by the Vendor, and for Dynamic, this price will be pegged at an outside tracked rate.&#x20;
* **Spread**: Finally, the spread percentage can be set, allowing the vendor to generate profit on top of the above set pricing.

In the image below, is an example of a deal set up in which the Vendor wishes to sell BTC they own, in return for GBP (British Pound). With their settings, the price will automatically be set as the market rate of BTC-GBP, and they will profit on each deal with a spread of 5%.

<figure><img src="/files/8qp20BGSb3zqbQGFISQy" alt=""><figcaption><p>P2P Vendor Type and Price Example</p></figcaption></figure>

### 2. Set the Total Amount and Payment Methods

Here, the Vendor can fine-tune the deal's options.&#x20;

On the left side of the screen are options related to the amount of crypto to sell:

1. **Total Amount:** How much of the crypto asset is the vendor willing to offer total, to all users. One thing to note here is that for the chosen amount, the Vendor must have sufficient assets in their HollaEx wallet.
2. **Limits**: Simply the minimum and maximum amount of the asset that can be bought by a single user in a single transaction.&#x20;

On the right side, the options for the fiat:

1. **Payment Methods:** Here, the vendor can choose the desired methods to receive fiat payment from, from those methods supported by your exchange.&#x20;
2. **Region**: Simply the region in which the deal is listed as taking place.

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

### 3. Set terms and automated response

Finally, the Vendor can write the terms, displayed to the user before making the deal, and also an automatic first response that will be delivered on a user entering the chat room with the vendor (perhaps letting them know how long a reply can be expected)

<figure><img src="/files/CgkAmHidYVQ4SQZXOHjQ" alt=""><figcaption><p>Terms and Response Page</p></figcaption></figure>

With this last page complete, the user can submit the deal and have it become live and ready to be taken up by another user.


# Admin Logs

Logging actions on your exchange is essential for the safe and secure operation of it, HollaEx provides the tools to give a bird's-eye view of all activity.

The Admin Logs are a vital tool that ensures you, as the exchange operator, have a clear view of all activity occurring on your exchange.

It can be found in the admin controls, on the sidebar. Once opened, you will be shown a comprehensive list of all actions on the exchange by your users and team members:

<figure><img src="/files/9JOS87yBqU3qQPyWDNXf" alt=""><figcaption><p>Opertar Logs Screen</p></figcaption></figure>

From here, you can scroll through or search for specific user/ team emails or IDs to narrow your search.

Each action will display the user who performed it, their [session](/how-tos/operator-control-panel/sessions) in which they did it, and a breakdown of what the action itself was. This will also display the user who was affected by it, and when it took place.


# Smart Chain Trading

Chain Trading allows your users to quickly perform swaps, even between assets that don't have a direct market available, ensuring as seamless an experience as possible.

<figure><img src="/files/8QVVhtpQP9DqPfvaJPBj" alt=""><figcaption></figcaption></figure>

## What is Chain Trading?

Chain Trading is a method in which two assets that don't have a direct market with each other on your exchange can be traded using a third intermediary asset (often USDT).

Behind the scenes, this operates by performing two swaps for the user's single trade: once with the held asset to the intermediate asset and then once from the intermediate to the desired asset.

Behind the scenes, this operates by performing two swaps for the user's single trade: once with the held asset to the intermediate asset and then once from the intermediate to the desired asset.

Chain Trading is available on all HollaEx plans.&#x20;

## How is Chain Trading Useful?&#x20;

Chain Trading is a great way to assist in the user experience by simplifying both the user and operator experience.

For the user, it minimises and simplifies the number of actions that are required to trade between two assets they own. For example, if your exchange supports USDT, BTC, and XLM, with markets for USDT/ BTC and USDT/XLM, but not for BTC/XLM, they will still be able to act as if there were a direct BTC/XLM market.

On the operator end, there are two primary benefits:

* Similar to the user, it simplifies the experience. Instead of having to set up markets between all assets on your exchange, you can focus on linking assets to your intermediary asset and not have to be too concerned about setting up more niche markets directly.
* It also opens up a new method of generating profit, as since a spread can (optionally) be applied, you can obtain profit on the users' trades that benefit from this convenience.&#x20;

### Benefits for Fiat Markets

One place Chain Trading can help out is in the use of the Fiat Controls. Generally, operators using the Fiat Controls will set up OTC Broker deals for the Fiat they have enabled on the exchange, and then, for the assets they want to allow Fiat <> crypto conversion for.&#x20;

Of course, it is always possible to set up this OTC Broker for the fiat currency and every crypto asset on the exchange, but this does require the labour of initial setup, and the regular checkup to ensure prices are as they should be, and assets are sufficient to maintain trading activity.

Chain Trading can simplify this significantly. With it, you can focus on a select market (such as whatever fiat and USDT), and with chain trading, users will be able to trade for the fiat asset and all other assets on the exchange that have a market with your intermediate asset.

As an example, imagine we provide liquidity to a GBP (British currency) / USDT market on the exchange, and this is running efficiently. Users could still trade for say XLM, DOGE, LINK, directly with their GBP, even if we, as the operator, have never considered these markets.

***

## Enabling Chain Trading

All HollaEx plans offer the Chain Trading feature. To enable it, navigate to the General section on the operator controls, then the Features tab, and scroll down and check the Chain Trading box.

<figure><img src="/files/2wpOixmmNIWErfLCBgzR" alt=""><figcaption></figcaption></figure>

## Chain Trading Configuration

The options for Chain Trading are dealt with directly from this same page and are, fortunately, simple.

Hit the green Configure button beside the Chain Trading feature name, and the menu in the following image will appear.

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

Here we have just three options:

* **Currency**: The source coin, which will be used as the intermediary asset between the held and desired asset. In most cases, this will be USDT, due to its common usage for pairings.
* **Source Account ID**: This is the account that will have a sufficient amount of the intermediate and perform the required trades, such that the user can trade between their two desired assets.
* **Spread**: This is a premium that is applied to the user who has requested the trade. This is defined as a percentage of the trade's value.

## Chain Trading Best Practices

* The source account is the linchpin of Chain Trading, and thus, it is crucial to ensure this account always has a sufficient amount of the intermediate asset to perform all required trades. In the case where this asset is not sufficient, this could lead to issues where one part of the trade is complete but the second is impossible, leading to issues with providing the user what it is they want.
* The choice of source account is left to the operator. This could be the admin, or it may be wise to set up a dedicated liquidity account that is used solely to be the middleman in chain trading.
  * This account should be given an account tier that offers them fee-free trading, to ensure that the trades it performs are not going to negatively impact its&#x20;
* The choice of the currency is vital. As a general rule, USDT will be king in this matter. On the HollaEx Network (and beyond), USDT is almost universally used as a pair for all assets. What this means is that you will minimise the number of trades necessary to provide the user with what they want.


# Auto Trader

The Auto Trader feature enhances the convenience of your platform, allowing users to set up trades at regular intervals, allowing for DCA strategies to be simple to implement

## What is the Auto Trader?

The HollaEx Auto Trader is a simple-to-use system that allows your users to automate their trading strategies, trading assets at regularly chosen intervals.&#x20;

## Who Has Access to the P2P System?

The HollaEx Auto Trader system is available for exchanges and uses that exchange's [OTC Broker](/how-tos/otc-broker) to execute trades.

## How to Set Up Auto Trading&#x20;

Enabling Auto Trading is simple. Head into Operator Controls as the exchange's admin, into General on the sidebar, and into the Features tab. From here, just click the Auto Trade option, and you are good to go.

Keep in mind that the trades that are executed using the [OTC Broker](/how-tos/otc-broker) must be set up beforehand, and as the admin, you must ensure that the liquidity it uses is maintained to ensure a seamless user experience. &#x20;

***

## User Flow on the Auto Trader

The use of the Auto Trader is almost as simple as enabling it.

From the Trade dropdown in the navigation bar, access the Auto Trader from its option.

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

The Auto Trader page will open. The Deposits, Markets, and History pages can be accessed from the top right links:

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

To set up a new recurring trade, the links either in the centre or the upper left of the page are used. These will open the following menus:

<figure><img src="/files/9y4UbJgZbatymKXVD6cG" alt=""><figcaption></figcaption></figure>

Here, the two assets are chosen. The available assets will be from the markets that are available from the OTC Broker. This list will include [fiat assets](/how-tos/fiat-controls) as well as crypto if you have created a relevant market in the OTC.

Alongside this, the amount, per recurrent trade, of the asset to be spent is chosen.&#x20;

{% hint style="info" %}
This will use the settings from that market's OTC Broker deal, and thus will only allow the minimum tradable asset to be selected. The user will be alerted if their chosen spend amount doesn't meet the market's threshold.
{% endhint %}

With these chosen, the frequency of the trade is chosen, between daily, weekly, or monthly trades, from the dropdown.&#x20;

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

Dependent on the selection, the subsequent menu will slightly differ.

### Daily Trades

Daily trades will simply occur at a proscribed time, on the hour, chosen at any time (UTC) by the user.

<figure><img src="/files/1d9xEalD5AD0igL5XuTs" alt=""><figcaption></figcaption></figure>

### Weekly Trades

Weekly adds the option to choose what day, and the same option for the trade time to occur.

<figure><img src="/files/06deK0UO61cforafOg5D" alt=""><figcaption></figcaption></figure>

### Monthly Trades

Monthly swaps out the day choice for the date.

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

***

After the timing of the trade, a simple description can also be entered.

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

Finally, the details of the Auto Trade can be reviewed or edited.

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

With it confirmed, the Auto Trade will not be active and viewable from the original Auto Trade page. From here, it can be paused or fully deleted.&#x20;

<figure><img src="/files/3Q1HxBNXh2euTRxksGk4" alt=""><figcaption></figcaption></figure>


# Referrals

With referrals, you can reward users on your exchange with rewards for encouraging growth, as well as provide trading fee discounts to the users they bring onto your platform

## What is the Referral System?

The HollaEx Referal feature is a way you can reward your current users by giving them a portion of the fees generated by another potential user, by them simply sending a link to sign up to your exchange.

All fees generated by that new user as they use your platform will be split between your platform directly and the user who sent this link, as well as the sending user being able to split some of their reward to give the new user a discount on trading.&#x20;

With the referral feature, you can encourage user growth in your crypto exchange, as well as reward those users who have the most positive effect within it.

***

## How to Set Up Referrals&#x20;

Enabling Referrals is simple. Navigate to *Operator Controls* as the exchange's administrator, then select *General* from the sidebar, and proceed to the *Features* tab. From here, simply click to enable Referrals.

&#x20;

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

With this done, you will need to configure the specifics of how your reward system will function. Click the green *Configure* button to open the following menu.

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

The options here set the following:

* **Currency**: This is the currency that will track earnings. Keep in mind this is not the currency that the user to be rewarded will receive, but simply gives them an easy method to monitor over time the amount that their referrals have paid out to them over time.
* **Earning Rate**: This defines, as a percentage, how much of the fees that are generated by the referred user will go to the user who sent the referral. The remaining amount will go to you as the operator as normal.
  * For example, assuming an Earning Rate of 50, if user A sends their link to user B, and user B executes a trade that generates 1 USDT of total fees, 0.5 USDT will be held for User A, and the remaining 0.5 USDT will be kept for the exchange [to be settled as normal](/advanced/the-network-tool-library/getting-more-interesting-orders-with-the-tools/settling-fees).
* **Earning Period**: The amount, in months, that a user who has referred another will continue to benefit from the split in fee amount. Setting this to zero will mean that the user who has sent the referral link will continue to benefit from the fee split indefinitely.&#x20;
* **Minimum Amount**: This is the amount, based upon the chosen currency above, must amount to before the user who has referred another can settle and receive their held fees into their wallet.
* **Distributor ID:** This is where the rewarding assets will be sent from. In most cases, this should be the same account that is where overall exchange fees are sent.

Once these settings are chosen, click Proceed to save them.

***

## Referral Flow

### Referrer Flow

The *referrer* is the existing user on your exchange who sends a link to another user to encourage them to sign up and use your exchange.&#x20;

In return for this, the referrer will receive a portion of all fees generated by the referee, dependent on the configuration the admin has set as detailed above.

#### Sending a Referral Link

To begin this process, the existing user should navigate to the *Earn* tab in the Nav bar on the exchange and access the *Earn Commission* page.&#x20;

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

On this page, the user will see a list of details of their past referrals, empty if this is their first.&#x20;

They can create the new referral link by clicking either of the links in the second box:&#x20;

<figure><img src="/files/3Wt8LqlrkjsGkbCVJj8a" alt=""><figcaption></figcaption></figure>

From here, they have a few options to run through; the first simply displays what the randomly-generated link will be.

{% hint style="info" %}
Note: This link address cannot be edited by the user themself, but can be by the admin. This is shown at the [end of this page.](#admin-controls-of-referrals)
{% endhint %}

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

Next, the user can decide how much of the earning rate they wish to receive, compared to the discount they give to the user they refer. This discount will last as long as the *Earning Period.*

This increments in steps of 10%, selected using the arrows, up to 10% below the maximum return rate decided by the admin.&#x20;

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

Finally, a review of the referral is shown and can be confirmed.&#x20;

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

From here, they can copy the link and send it to whoever they want to bring on. This code can be reused by multiple different sign-ups, and the referral count will track this number. This means that a referrer can use different discount codes for different groups of people and earn different amounts depending on their preferences.

Once the new users sign up, the count will increment by one, and any activity they generate with trades will begin to be tracked in the Earnings and History sections.

***

#### Tracking Referral Rewards

As a user brings on others, they will be able to see what they have earned, as well as settle the amounts once they attain the minimum amounts defined by the admin.

The first Referral Earnings page, after some activity, will begin to show the number of sign-ups they have encouraged, as well as in the top section showing their total earnings over time, and any amount they have but have not settled into their own wallets.

From here, they can click the blue Settle button to instantly transfer the unsettled amounts to the relevant wallets they have, assuming the total is over the admin-defined minimum.

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

In the history tab, the user can simply see a record of all the times they have settled their collected earnings, with the relevant details.&#x20;

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

***

## Admin Controls of Referrals

Admins can view both the referring user's data related to their codes and the referred user.

For any user, by navigating to the referral tab of that user's page, the admin can view current referral links, the details of that link- percentages applied, and sign-ups from that link.

Here, an admin can also create a new link specific to that user. This can be used if you have a particularly successful referring user and want to provide them with a more visually appealing personal code.

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

Once saved, that user will also see this new link in their link list, and can copy and paste from here as normal.&#x20;

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

Finally, an admin can view if any user has been referred by another; this can be seen again in any user's Referral tab, in the top left, as shown.

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


# Profit & Loss Tracking

P\&L Tracking allows users to see the performance over time of all of their exchange activity, keeping them in the know about what is working (and what is not)

## What is Profit & Loss Tracking?

Profit & Loss (P\&L) Tracking is an easy way for users to see a quick summary of the change in value of all assets over the exchange, all kept on a single, easy-to-understand screen.

With this, users can monitor and, if needed, alter any trading strategies they are using on your exchange.&#x20;

***

## How to Set Up Profit & Loss Tracking <a href="#how-to-set-up-referrals" id="how-to-set-up-referrals"></a>

Enabling P\&L Tracing is easy. Navigate to *Operator Controls* as the exchange's administrator, then select *General* from the sidebar, and proceed to the *Features* tab. From here, click to enable *P\&L Tracking*. On refresh, the feature will be instantly accessible.

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

***

## Using the P\&L Tracker

The P\&L Tracker is likely the single easiest feature on the entire HollaEx exchange. By navigating to the navigation bar, hovering over *Wallet*, and then clicking the *Performance* option in the dropdown, they'll have used it the moment they look at the page.

<figure><img src="/files/2utTgAYsYi2hzpuYtwqu" alt=""><figcaption></figcaption></figure>

The first tab is a simple line chart, showing the gains and losses over time. This will be affected by any deposits and withdrawals, and with the change over time of any assets' value they have in their wallets.

From here, they can change the time period to look at from a week up to 3 months, using the buttons above the chart.&#x20;

The value shown in the top right will reflect the total summed value of all stored assets, crypto or fiat, converted into the native currency of your exchange. Hovering over any data point on the graph will show the value at that particular time.&#x20;

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

The second tab, *Balance History*,  simply lists all the assets stored and can be filtered to show the total amount of any assets from any date, going back three months, using the date selection on the top right.

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


# Assets & Trading Pairs


# Add New Assets & Trading Pairs

## Add a New Coin (Already Supported by HollaEx)

Adding a new coin to your exchange can be easily done through the exchange's operator control page. Go to the "*Assets*" tab on the operator's control, or via the Assets tab in the Dashboard. You'll see the list of coins you currently have on your exchange.

From there, you can proceed to add a new coin by clicking the "Create/add asset" button

![](/files/uTGRzrm6wkw4OpUMMWnf)

HollaEx Assets are those assets that have been added by HollaEx itself, with 'Other Assets' being assets that other Exchange operators have added (see the section below) which you will also have access to.

![](/files/cr8H4IU52dK80dootO7s)

Click the '*Asset'* dropdown on the popup. You can see all the available coins you can instantly add to your exchange, or search with either the coin's contract address or name.&#x20;

With the coin you want to add selected click '*Next*' to proceed.

![](/files/Jq9aH45hgMWVDQd7TlNc)

Check for the details, and click '*Confirm*' to apply.

![](/files/cRJmb2JFqyJMQfX5tVMG)

Ta-da! The new coin has been added to your assets list and is now instantly available on your exchange.

![](/files/WQf3m1cFVF722qVLbHb4)

***

## Add a New Asset (Not Already Supported By HollaEx)

It may be the case that the current list of assets doesn't have what you currently want. Alternatively, it could be the case you have or plan to create new assets of your own. In either case, don't worry, both follow the same process.

The number of asset additions you can do is dependent on your particular plan type, [outlined here.](https://www.hollaex.com/pricing)

Let's go back to the *'Create/add asset'* popup and click the *'Add your asset'* link, just above the green buttons.

![](/files/NVx1Kg0H9dOyOqSUjUvo)

The following popups will ask you a few questions about your new coin.

{% hint style="info" %}
If you need to add a new fiat asset for use in the fiat controls and can't find your desired fiat currency, please get in touch with the <support@hollaex.com> team for help with this.
{% endhint %}

In this case. let's follow the process for a blockchain asset. Our first option is to choose what network the asset uses.

![](/files/5Xaanzq2XDamMK64hvZA)

{% hint style="info" %}
In case your Blockchain is not already supported, please contact <support@hollaex.com> to discuss how we can get this arranged, as it will require a different process than that below.
{% endhint %}

To find your asset, all that is required is to insert the asset's contract address in the top input box, click 'Search', and let the system find the relevant details for the asset.

With this done, you can select the asset colour (generally border for the asset's icon through the exchange), description (displayed on that asset's exchange page), and icon (75x75px recommended).

Finally, making the asset '*Public*' means you have the choice of either keeping the asset only available to your exchange or choosing to make the asset available to other HollaEx exchange operators, which could benefit how many users are exposed to the new asset.

![](/files/gXEsSqZ2ldIVTDCThwuD)

Following this, input the amount your asset is priced at (in US dollars). Don't stress too much over the exact amount here, as this is more likely to be handled in the OTC Broker (where you choose the price) or the market at large (if the order book is being used).

![](/files/D2GrUPLpUQbXcvlBh2FB)

Next up, choose the parameters regarding withdrawing your asset.

![](/files/ahbVAZNpJ3iNvikD6eoe)

Finally, review your values and confirm. With this all done, your asset will be marked as pending in the *Assets* list, with a little orange clock icon beside its name.

![](/files/AisKswSVb9CyPXzzPhfB)

With this done, contact <support@hollaex.com>, to let them know you wish for the asset to be added and they will guide you through the process.

The next step is the easiest, simply wait a handful of days and your new asset should now be ready and good to go on your exchange!

***

## Add a New Trading Pair (Already Supported by HollaEx)

Similar to adding new coins, adding trading pairs can be done easily through the HollaEx Dashboard in the '*Trading*' section. You'll see the '*Create/add Market*' button in the '*Markets*' tab of it. Click this button to proceed.

![](/files/KxIDPu10x35Ji2lEQzVY)

The popup then shows all preset pairs that are compatible with your exchange setup with a dropdown. Select whatever you want through it, and click 'Next' to continue.

![](/files/f4TBXezVpCOONNhLQLzW)

Click '*Next*' to proceed again after checking the pair details.

![](/files/DpOm1HNsqcfWS03rCK2H)

You'll now see that the new pair has been added to your 'Pairs' list. Voila!

![](/files/QJquIwxuDQz4tUs9eHHW)

***

## Create a Fully New Trading Pair (Not Already Supported by HollaEx)

You can also add a fully new trading pair based on the coins you have on your exchange. Click the '*Create pair*' button, and select the '*Create a new market*' option on the popup.

![](/files/m7EFgsgxWiXsC99DGiEb)

Set the base asset and priced asset for your trading pair, and click '*Next*'.

![](/files/l9TwGxBHlnvsQv5LTwze)

Set the pair details here and proceed.

![](/files/zLYEA0t5NbqevmGZrjuA)

Review the configuration and click '*Next*'.

![](/files/3QHTxo7MX75YhuoyMpOy)

Now get in contact with the *<support@hollaex.com>* team, and let them know your exchange and what the newly added trading pair is and they will assist you in getting it added and finalised.

Once this gets verified, you'll see the pair on your exchange, and users will be able to access the orderbook for it.


# Configure Asset Parameters

When creating or configuring a new asset in the system, you are asked to provide several parameters. These values determine how the asset behaves across deposits, withdrawals, and the general valuation system within the exchange. Each parameter plays a role in ensuring consistent behavior, preventing misuse, and maintaining a smooth user experience.

### **Estimated Price**

Estimated price is used in situations where the system cannot find any existing market price for the asset. For example, when the user checks the estimated value of their wallet balance, this value is used only if no market data exists for the asset.

Although you have the option to manually set this number, it is generally recommended **not** to do so. The price should normally come dynamically from existing markets or external feeds. Manually setting this is only appropriate when the asset has **no markets** at all or does not receive any price points from the exchange.

### **Minimum Amount**

Minimum amount determines the smallest value of the asset that is accepted for withdrawals or deposits.

For withdrawals, users will not be able to withdraw anything below this amount.\
For deposits, any deposit lower than this threshold will not be processed into the user’s wallet and it is treated as a “dust-prevention” measure to stop extremely small deposits from causing operational issues.

Setting a reasonable minimum amount helps ensure users do not create tiny, unusable balances or unnecessary blockchain traffic.

### **Maximum Amount**

Maximum amount defines an upper limit for both deposits and withdrawals. This is the largest amount a user can withdraw in one request, and also the maximum amount the system will accept as a deposit.

It’s important to remember that the maximum withdrawal is a **hard cap**. Other limits (such as tier-based limits) may still apply on top of this, but the global maximum amount acts as the absolute boundary for any withdrawal request.

### **Increment Amount**

Increment amount determines the smallest allowable unit for the asset. This value is used when rounding numbers, enforcing decimal places, and validating user withdrawal inputs.

For example, if the increment is set to **0.0001**, then the system will only accept values that match that step size for 4 decimal points. Any value that does not align with this increment will be rejected. This ensures consistency in asset precision and prevents unusual fractional amounts from entering the system.

### **Allow Deposit**

Allow deposit simply indicates whether users are permitted to deposit this asset into the exchange. When enabled, deposits work normally. When disabled, the system will reject incoming deposits or block the deposit flow depending on the blockchain behavior.

This option is useful during maintenance, asset migrations, or situations where the asset should not be deposited temporarily.

### **Allow Withdrawal**

Allow withdrawal indicates whether users can withdraw the asset. If enabled, withdrawals process normally. If disabled, users will be unable to withdraw until it is re-enabled.

This is typically used during wallet maintenance, blockchain incidents, or times when the asset needs to be temporarily frozen for operational or security reasons.


# Configure Pair Parameters

When creating a new market, you are asked to provide certain parameters before creating the market. These parameters are used in the orderbook to specify certain attributes and restrictions in the orderbook.

**Min/Max Price:** You will want to have a certain range where the price can fluctuate to prevent price manipulation. You have the option to use min and max price to set that range. Min simply refers to the minimum price user can place an order and max refers to the maximum price user can place an order. Please keep in mind that you are not allowed to place an order exactly on the min and max.

**Min/Max Size:** Just like how you set min and max price to have an accepted price range in your orderbook, you could do the same for order size. A minimum accepted amount and maximum accepted amount for the orderbook. You will want to have a minimum set to prevent 'dust' orders. These small orders can cause issues and make your exchange slow. Max is also useful when you want to prevent very large orders from being placed at once.

**Increments:** There are two increments in an orderbook. One is used for price and the other is used for size. Price increment simply refers to each price level you can create in the orderbook. Let's say the price increment is set to 0.001. Now that means prices with 4 decimal points such as 1.0001 will be rejected. You could only make price increments of 0.001 or a multiple of 0.001. For example:

1.001 -> ✅

0.1 -> ✅

0.1001 -> ❌

5.0005 -> ❌

A general rule in setting increments for the price would be to set it as 0.1% of the actual price (this price is based on the quote asset). The same goes for increment size which is used to adjust size increments. This is very important to set correctly to avoid dealing with many decimal points that could be confusing for traders.

**Circuit Breaker:** This is a relatively new feature introduced in HollaEx 2.1 where users can turn on this mode to prevent large price fluctuations. In certain markets, you do not want to have an immediate price drop/rise (say 20%) very quickly. Turning this feature on would help prevent these price changes over short periods of time. The Circuit breaker has smart logic and simply having it on would make sure to avoid large price fluctuations. In case you are dealing with a market with large price fluctuations, it is better to have this feature off, otherwise, it would cause many halts in trading, resulting in a bad experience.


# Set up the SMTP Email

{% hint style="info" %}
For HollaEx Cloud users, there's a built-in feature to provide free (and easy!) SMTP mail server support. Please check the 'Hosting' - 'Domain' section on the [HollaEx Dashboard](https://dash.hollaex.com).\
\
For details on this, check the following [page](broken://pages/rSfjJtTLLiI1n2VaUCjO).
{% endhint %}

![](/files/-MZQWTiTu5WQqcyZVIa-)

After all the years since email was invented in the 1970s, it is still a crucial part of many internet services. HollaEx Kit is no exception! The email service is used to notify users about their activities, send updates to the exchange operator, and is mandatory for security verification.

HollaEx Kit relies on the standard "SMTP" protocol. This is a technical standard that most email service providers support. Below you can find a list of services offering this service.

* [AWS SES](https://aws.amazon.com/ses/?nc1=h_ls)
  * [Set up SMTP with AWS SES](/how-tos/set-up-the-smtp-email/set-up-smtp-with-aws-ses)
* [Mailgun](https://mailgun.com)
  * [Set up SMTP with Mailgun](/how-tos/set-up-the-smtp-email/set-up-smtp-with-mailgun)
* [SendGrid](https://sendgrid.com/)
  * [Set up SMTP with SendGrid](/how-tos/set-up-the-smtp-email/set-up-smtp-with-sendgrid)
* [Gmail](https://gmail.com) (Only for testing, not recommended for production)
  * [Test the SMTP with Gmail](/how-tos/set-up-the-smtp-email/test-the-smtp-with-gmail)

## SMTP configuration on the HollaEx Kit

Once you log in to your HollaEx exchange as an admin, go to the Operator Controls, and you'll see the section for configuring SMTP details in the '**General**' tab.

![](/files/OX5zwJYcmwoztn8AbrEQ)

From here, you can put the SMTP credentials that you got from the email provider.&#x20;

![](/files/esLT5l5P5bpKmyLsm87w)

This sample image above uses the configuration with SMTP credentials offered by [AWS SES](https://aws.amazon.com/ses/?nc1=h_ls).

In addition to configuring your settings, at the bottom of the inputs, using the 'SEND ADMIN TEST EMAIL' link will allow you to test whether these settings have worked by sending a test email.

## Troubleshooting

![](/files/-MZQd55w14TQ1tEvxQNz)

You can browse HollaEx Kit server logs to see more details in order to find issues with your mail server provider.

```
hollaex logs --target api
```

The command above will show the logs from your API server. Searching for the keyword `SMTP` It would be the fastest way to find what you are looking for.


# Set up SMTP with AWS SES

![](https://media.vlpt.us/images/chrishan/post/bc1ef64f-c77d-4434-bc03-31d365c8b5b5/9fe553f6b8b7a9bdb9906ad49e48e40c091358.png)

[AWS SES](https://aws.amazon.com/ses/?nc1=h_ls) is a managed email service made by Amazon AWS. Since many production-level online services are relying on the AWS already, choosing SES for the SMTP is generally a good choice both for stability, and centralizing all server-related stuff into one management console.

## Get started

![](/files/-MZlI9fORC7ABLlvl5gy)

Once you are logged into your AWS account, go to the 'Amazon Simple Email Service' (SES) menu. From here, you can add your domain that you want to use to send emails. It is always recommended to **enable** 'Generate DKIM Settings' for better email security.

![](/files/-MZlJaj1owgQLndqAAsh)

Now, you should add the values that SES gives to your DNS configuration. Adding these values will help the AWS SES to verify the domain ownership.&#x20;

![](/files/-MZlKoNkvmdj9rlKtdgs)

The domain will be marked as 'verified' once the DNS configurations are set properly.

![](/files/-MZlL8VeYF5myNPB63-8)

![](/files/-MZlLFdZg0HakEyB6YZ2)

![](/files/-MZlLpN26co6GyRwfRIj)

Next go to the 'SMTP Settings' on the side menu, and proceed to create SMTP credentials. These credentials will be used on HollaEx Kit later for configuring SMTP. Don't forget to save the generated credentials.

## Configuring the SMTP

{% hint style="info" %}
For the **SMTP server** endpoint, it is **different based on your AWS region**. Please check the [AWS official doc](https://docs.aws.amazon.com/general/latest/gr/ses.html) for more information.
{% endhint %}

![](/files/-MZlMqtRpIitRgyXkJJt)

Your configuration should be similar to the screenshot above.


# Set up SMTP with Mailgun

![](https://blog.kakaocdn.net/dn/JE9dm/btqv9rNpPiS/y5mzKYzSgwWIfnTMHWr2Bk/img.png)

[Mailgun](https://www.mailgun.com/) is an easy-to-use email service that is oriented towards developers. It provides straightforward API guidelines and interface to make your app to send emails like a boss.

## Get started

![](/files/-MZS86AyBplIydGHFe-3)

Go to the Mailgun website and sign up first. It might require you to verify your mobile phone number and credit card. But once you verify all of these, you will get free 3 months trial, so not a bad deal.

![](/files/-MZS8c0G6unz1uOV0C5i)

Once you finish all verification steps, go to the 'Domains' section at the sidebar, and click 'Add New Domain' to proceed.

![](/files/-MZS90aHpPab8qQY1d7c)

Provide your root domain here. For example, if you want to send emails with `dev@example.com` email, you should type `example.com` here.

![](/files/-MZS9QYls4muMssDnwfl)

![](/files/-MZS9ozXkO5VrGCdFh0N)

Now, based on what it shows, you should add those values to your domain's DNS configuration. Add all of those values step by step, and click verify button at the Mailgun console to go next.

![](/files/-MZSAENRj_MH0FYl8cUW)

![](/files/-MZSAHAOddlbX83SNy4E)

It's mostly done now. Once your domain is verified, click the 'SMTP' menu, and you'll see the credentials for it right away. Based on that, all you need to do is set it up on your HollaEx exchange.

## Configuring the SMTP

![](/files/-MZSB6PhQDeKDcbdJKZK)

Your configuration should be similar to the screenshot above.


# Set up SMTP with SendGrid

![](/files/-MZRp1bUqjWv36D3Paf1)

[SendGrid](https://sendgrid.com/) is one of the well-known email service providers, and it can be integrated easily with HollaEx Kit. In this article, we will go through step by step on how to set up SendGrid with HollaEx Kit.

## Get started

![](/files/-MZRplQKDT8cRXs1EySE)

Simply go to the SendGrid website and sign up.&#x20;

![](/files/-MZRqLwWdvfzIrB56e31)

The first thing you should do after signing up is create a new sender. Go to the 'Sender Authentication' section in the sidebar and continue.

![](/files/-MZRro9IxNnt7CzFGEgh)

Make sure to go through the email link verification afterward.

![](/files/-MZRsiRaG3OSeksB8kLj)

Now, you should make an API key for SendGrid. Go to the 'API Keys' section in the sidebar and create an API key. You should give at least '**Mail**' **permission** to it!&#x20;

Don't forget to keep it somewhere once it's generated.

## Configuring the SMTP

To use SendGrid for sending emails from your HollaEx Kit exchange, refer to the details below and set it up on your exchange.

{% hint style="info" %}
Please check SendGrid's [official docs](https://sendgrid.com/docs/for-developers/sending-email/integrating-with-the-smtp-api/#smtp-ports) for more information.&#x20;
{% endhint %}

* Host: `smtp.sendgrid.net`
* Port: `587`
* Username: `apikey (It's just a string 'apikey'!)`
* Password: `<YOUR-API-KEY-VALUE>`

![](/files/-MZRxc5BKvcF5XCHE4b8)

Your configuration will look like the screenshot above.


# Test the SMTP with Gmail

![](/files/-MZQgOo5mxCnvJ4-poy3)

Everyone loves Gmail! It's easy and very popular. Well, not really for the production server integration to be honest. Gmail does provide an SMTP integration feature, but there are some limitations. That being said, for testing purposes, it's useful and free to use.

## Prerequisites

First of all, you should allow the 'Less secure apps' on your Google account. Google is marking the SMTP access as "insecure" based on their policy, so this is required to make your account for the SMTP emailing.

You could enable it [here](https://myaccount.google.com/lesssecureapps) in Google's Less secure app access section.

If you are using a 2FA, you might need to go through one more step. Google asks you to generate an "[App Password](https://support.google.com/accounts/answer/185833?hl=en)" in order to use a less secure app. Which is like a temporary password only for your less secure app access.

You can make an app password [here](https://myaccount.google.com/apppasswords). Make sure to make a memo of the password coming from Google somewhere. This is required on the HollaEx Kit Email Configuration.

## Configuring SMTP

* Host: smtp.google.com
* Port: 465
* Username: `<YOUR-FULL-GMAIL-ADDRESS>`
* Password: `<APP-PASSWORD-YOUVE-GENERATED>`

![](/files/-MZQjv-ElX-iomN6eRtS)

The configuration will be similar to the screenshot above.

Now, you'll get emails from your exchange with your Gmail account. Unfortunately, Google only allows 100 emails per day with SMTP. Which is not enough at all for serious production exchanges. We recommend you  look for a proper SMTP email provider such as [AWS SES](https://aws.amazon.com/ses/?nc1=h_ls) or [Mailgun](https://mailgun.com) for production use.


# Enabling reCAPTCHA

ReCAPTCHA is an invaluable line of defense in keeping malicious spambots at bay, even better it's mostly free and quick to set up.

## What is reCAPTCHA?

reCAPTCHA hails back many years and has seen a few iterations throughout its lifespan (many won't have the fondest memories of trying to prove humanity through some of the early text decipherings).&#x20;

<figure><img src="/files/EOja7gFqCqepmE8HwUea" alt=""><figcaption><p>Remember these?</p></figcaption></figure>

The latest version, though (v3), is brilliant as most users will not even know it is in effect. This version uses behavior analysis to determine whether a visitor is legitimate, and only in more suspicious cases, it asks the users to hit the button to prove they are a human and not a bot.

<figure><img src="/files/Mn12dpnNIf65sHIotLfS" alt=""><figcaption><p>You have almost certainly seen this guy</p></figcaption></figure>

## Setting Up reCAPTCHA

{% hint style="info" %}
📺 Watch on YouTube!: [Protecting Your Site with reCAPTCHA](https://www.youtube.com/watch?v=Wuw_lpxAYzQ)
{% endhint %}

Enough background chat, let's get it set up (it won't take long).

We only need two browser tabs open.&#x20;

1. Your Exchange (logged in as the admin)
2. Google's[ reCAPTCHA page](https://www.google.com/recaptcha/admin/)

{% hint style="info" %}
If you have never set up a reCAPTCHA before, this reCAPTCHA link above will take you straight to the create page.&#x20;

If you have, then you will go to the admin console, and add a new reCAPTCHA with the plus icon in the top right
{% endhint %}

Once on the 'Register a new site' page, you will see the following:

<figure><img src="/files/j5wi7ne3PA4BAxbhY4cf" alt=""><figcaption><p>This is the hardest part of the whole setup</p></figcaption></figure>

Each section is explained by Google well, but for clarity:

1. **Label** - This is just how the reCAPTCHA setup will be named in your reCAPTCHA Admin console once set up.
2. **reCAPTCHA Type** - This bit is important; ensure to **choose reCAPTCHA v3 only**, as the other may cause issues (v3 is the best for user experience as well).
3. **Domains** - This is simply the root domain where your exchange is hosted.
4. **Owners** - Where emails of any alerts will go, most likely you will choose your admin email

With those options filled in, accept the terms, decide if you want owners to receive alerts or not, and click submit.&#x20;

Once submitted, you will receive two keys as follows. Now it's time to stick these two into your exchange.

<figure><img src="/files/kIxDM1hn3GzRonqa0Q6t" alt=""><figcaption><p>It has even got a nice easy copy button for each key</p></figcaption></figure>

Head into the operator controls of your exchange to *General* -> *Security*. In the middle of this page, you will see a *reCAPTCHA* section with two input boxes.

Now take those two generated keys from Google, copy and paste each one into the relevant box, and hit save - easy as that!

<figure><img src="/files/fMCHXgrDsAeXTKjHcSpx" alt=""><figcaption><p>General -> Security -> Paste Keys from Google and Save!</p></figcaption></figure>

With all these steps done, reCAPTCHA is now protecting your exchange!&#x20;

Give it a little while, and then check out your login page. If everything has gone to plan, you should see a little reCAPTCHA icon in the bottom right, assuring you and your users that the exchange is protected.

<figure><img src="/files/cfvJKzhQtvvEDUMJ95YN" alt=""><figcaption><p>The seal of approval!</p></figcaption></figure>


# Operator Roles

For exchanges being used by larger teams, the HollaEx v2.15 Singha update included a new suite of tools, to better serve large teams.

## What are the Enterprise Roles Tools?

With the advanced Roles tool, Enterprise exchanges can get into the details of what is available to all team members involved in the successful running of the exchange. This can be done by either updating existing roles or adding entirely new ones.

## How to Set Up Enterprise Roles

By default, all exchanges come with 8 standard roles: Admin, Manager, Supervisor, KYC, Communications, Announcer, Auditor, and Support. These roles have set permissions that are discussed on the Roles page in the Operator Control Panel section of these docs.

Those Enterprise operators who want or need to expand on this default can do so easily.&#x20;

By navigating to the Roles page, found on the sidebar of the Operator Controls, and then the second tab available to Enterprise exchanges, Roles, we will find the options available.&#x20;

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

From here, we can see the default roles and see exactly what each can do from the 'Edit Permissions' button associated with each. Opening this, we see the vast number of options available:

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

In the image above, we can see the menu shown when editing an existing, default role, in this case, the *Supervisor* role.

From here, we can change the name, the colour associated with that role, and then the vast amount of options in the Permissions menus. Due to the number of options here, it's not possible to go through and explain each in depth, but, as with any issue you have when working with your Exchange, the friendly team at the <support@hollaex.com> email will be happy to clarify any that are not immediately obvious to you.&#x20;

There are three menus we can look through:

* **API Routes:** This is the largest list of options, and covers most functionality on the exchange, like checking user balances or creating deals on accounts.&#x20;
* **Kit Configuration**: These options are linked more to changing the exchange's operation. Options like changing visuals or specific fees can be found here.
* **Kit Secrets**: The final menu offers more 'backend' options, such as whitelisting IP addresses or managing the exchange's security.

***

### Creating New Roles and Example Roles

Beyond just editing the existing Roles, there may be a need to create more specific roles. This can be done by clicking the *Customize A Role* card at the end of your existing roles.

This will open a similar-looking menu to what was seen in the image above, where, by default, all permissions are turned off, and you can manually pick which to enable.

From this menu, each role can be assigned a name and a colour. The badge that each role will receive will be automatically generated based on the name.

#### Examples

For some examples of roles that may be of use:&#x20;

* As your P2P markets ramp up, you may encounter an increase in disputes that need extra manual attention in order to be resolved. In this case, the operator can create a new dedicated **P2P role** for resolving **disputes** that come up from the **P2P** trading environment:

<figure><img src="/files/Ok9kPj6ToMgGnFpbhOgt" alt=""><figcaption><p><em>Simply check the <strong>P2P</strong> related check boxes, create the role and designate the role.</em></p></figcaption></figure>

* As your exchange business scales, the frequency of communication naturally increases. Assigning a team member to a marketing-focused role can greatly streamline this process. You can easily achieve this by granting permissions related to **Announcements**. Additionally, this role can be expanded to include **Kit Config** permissions for broader content management responsibilities:

<figure><img src="/files/alE7wbI3KElSuU7UvRlR" alt=""><figcaption><p>Designate a member of a team to handle only the Announcements on your platform.</p></figcaption></figure>

* Set up an **Audit** role for compliance purposes:

<figure><img src="/files/6mGV3PFb0elW1Q2nku64" alt=""><figcaption><p>An Audit role can be created that can GET the data &#x26; events they need for compliance.</p></figcaption></figure>


# Sub Accounts

Sub accounts give the means to safely split allowing operators and users to segment risk and control access

## What are Sub Accounts?

Sub Accounts are a way to split a single main account into multiple buckets. These buckets will then have their activity and balances segregated from the main, each serving its own purpose, with defined permissions as well as being operated by separate users if required.

## How To Set Up Sub Accounts

Sub accounts are available to all operators and users, assuming your exchange is using a version beyond v2.17.

All accounts on the exchange can find the Sub Account settings by navigating to *Settings* in the sidebar and then the *Account* tab.

From here, the top menu will allow management of sub-accounts. On the first visit, the account itself will be listed.

To add a new sub account, click Create Sub Account for the following:

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

From here, we can create the sub-account:&#x20;

* **Type**: Here, the account can either be a new sub-account using actual login details filled in at the bottom of the form. A virtual email will be a sub-account that is accessed via the same login details as the main account.
* **Name/Label:** Chosen name for reference.
* **Color Code:** Color displayed on the sub accounts label.
* **Email:** if a 'Real Email' type is chosen, this will be the email used on the login.
* **Password (and Confirmation Retype):** Likewise for the login password of the new sub-account.

## Sub Account Behaviour

For the rules regarding Main and Sub Account behaviour:&#x20;

* A main account can create **additional child accounts** under the same owner.
* A main account can transfer funds in and out of the sub child accounts at any time.
* Users of the sub child accounts can't withdraw from the account (only the main account can 'transfer funds out')
* Each child account can be either a **Virtual** sub account or a **Real Email** sub account.
* Each sub-account can be used for **separate strategies or business lines** (e.g., treasury, trading desk, client funds, internal testing, future margin trading).

### Real Email Accounts

This is an account directly tied to another person's email address, and will be logged into with the details provided on creation, in the same way as a normal account.&#x20;

If the email does not currently exist on the exchange, the new user will receive an email inviting them to confirm the account's creation.

For the rules specific to Real Email Subs:

* Real Email sub accounts **cannot withdraw**, but they can deposit, trade, and use almost every other standard function.
* In order to withdraw funds from the sub account, the main account holder must use the 'Transfer Out' function within the Sub Account page while using the Main Account

### Virtual Accounts

Virtual accounts differ in that they have no email or password, and so can't be directly logged into. This means the only way to access them is via being logged in as the main account and using the switch option to change over to that account.

Potential use cases could be, but are not limited to:

* Running a higher-risk trading strategy in its own pocket of funds
* Testing new markets or bots without touching the main balance
* Also can't be withdrawn from directly (Main Account must use 'Transfer Out' functions)
* Preparing for features like future margin trading in a clean, isolated environment

***

## Examples of Potential Use Cases

Sub accounts open up a lot of practical patterns:

* A corporate exchange client gives a trader access to a **single Real Email sub-account** only, so the trader can work without touching treasury.
* An exchange team separates **operational funds** from **fee revenues** or **marketing allocations**, each in its own sub-account.
* Higher-risk strategies live in dedicated Virtual sub accounts, so they don’t impact the rest of the balance.
* Friends-and-family accounts are created with tightly scoped permissions and no withdrawal capability.

Sub accounts give structure to what is otherwise just “one big wallet”, while still keeping the experience simple for the main account holder.


# Shared Accounts

Shared accounts allow the segregation of funds over several 'buckets', either for a single user seperating trading funds, or for collaboration among multiple users on the same base account

## What are Shared Accounts?

Shared Accounts are a high-trust option available to users of your exchange, allowing multiple users (and their respective logins) to access the same account.&#x20;

In practice, this lets you delegate daily operations while keeping ultimate ownership and top-level oversight clearly defined with the original account holder.

Think of shared accounts as a way for serious business users to run their exchange account like a **team workspace** instead of a single-user login.

## How to Set Up Shared Accounts

All users on the exchange can set up a shared account by accessing Settings in the sidebar, then the Account tab. In the Account Sharing card, the hyperlink will allow for setup (warning the user about the potential risks of a shared account).

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

From here, the screen will display all other users that the user has allowed to share with their account, and in the second tab, vice versa.

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

From here, the user can follow the menu steps to share. This requires the email of the other user (who must have an account on the exchange already), as well as assigning a label.

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

This will then display in the Shared by You tab, and this email will be able to access this account from that email.

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

***

## Example Use Case- Market Making Together

A common use case for Shared Account is market making:

* You create a dedicated market-making account on your exchange.
* You then share that account with other team members in your high-trust business network.
* Those team members can monitor, adjust, and act on market-making activity while keeping everything in one place.

This keeps sensitive operations centralized, while still letting multiple people work together on the same strategy.


# Email Customization & Audit

Once your SMTP email has been configured, you can further customize the emails your users receive, and also set up an audit email to monitor what has been sent by your exchange.

{% hint style="info" %}
Ensure that you have set up your SMTP settings. Descriptions on how to do these with some popular providers can be found at the following: <https://docs.hollaex.com/how-tos/set-up-the-smtp-email>
{% endhint %}

## Customizing Emails

Located under the SMTP configuration settings, the options for customizing email templates can be found.&#x20;

![](/files/ZmO3asCVifpkOnuJDQs1)

The first option is to choose from the dropdown what email language you wish to customize. This dropdown contains all the languages you have set on your exchange.&#x20;

![Here you can see how by changing the language to French, the Account Upgraded email I am now editing is just for the French version of this email](/files/o44XEzb95Uf44o4Y55ST)

After selecting the email's language, from the second dropdown, choose what type of email template you wish to edit. There is a wide variety of these emails, so you can use the search function to assist you.

Examples of some of these templates:

* Login alerts
* Bank verification
* Password changes
* Among many others

![](/files/sQwbMYT37cSlVag8JXcg)

With your email type chosen, you will see the default title and email content. These can both be easily changed, the title via the top text field.&#x20;

The email content uses HTML and can be edited as much or as little as you want. Once you are happy with all your changes, click the green 'Save' button to apply them.

![](/files/U4Svzcm5o7EjHE57geVj)

## Email Auditing

In addition to customizing emails, an auditor's email account can also be set. This account will receive copies of all important emails sent out by the exchange. Simply enter the desired email into the text field and save.

![](/files/R8sVlMB60n6dNl1Wx4Qb)


# DeFi Asset Staking Process

Staking DeFi assets requires your own wallet outside of the exchange. To start with staking, you first need to establish a connection between the wallet and HollaEx. Once connected, you can stake and start earning directly from your wallet.

1\. Go to the Stake page on pro.hollaex.com/stake. Click the ‘CONNECT WALLET’ button in order to connect your wallet.

![](/files/umu1EBWOUVBMN7mMC3i0)

2\. If you haven’t installed Metamask on your browser, proceed with installing Metamask into your browser through <https://metamask.io/download.html>.

![](/files/NBS2ncnD6TVsYoz0JReV)

3\. Once you are done with the installation, you can see that the Metamask icon is added in extensions.

![](/files/koOxZeChDhcKtIe3CHZU)

4\. For your information, you can view and copy your Metamask address by clicking the Metamask icon.

![](/files/rKa3kjSUPVeGdqRZs0ps)

5\. In order to add HollaEx token(XHT) to the list, you first should add XHT on Metamask by clicking ‘import tokens’.

![](/files/ULkO3RH21D3XjA2INURp)

6\. Click the ‘Custom Token’ tab and it will require the XHT contract address.

{% hint style="info" %}
XHT token contract address : 0xD3c625F54dec647DB8780dBBe0E880eF21BA4329
{% endhint %}

![](/files/QZGxiSzBzaRT4t1cIC4N)

7\. The XHT contract address can be searched and copied through Etherscan as well.

![](/files/xYGEsAxXTbRh9Ibaaedd)

8\. Once you paste the XHT contract address, the token symbol and token decimal will automatically appear. Click the blue ‘Add custom token’ button below to proceed.

![](/files/NG2uw2uEpySI6M02lCXp)

9\. Click the ‘Import Tokens’ button and XHT will be successfully added to the asset list.

![](/files/XWFRmPPPJrbtJ0meVUuy)

10\. After XHT is added in Metamask, click the ‘CONNECT WALLET’ button again.

![](/files/8tEpQqjQrNei0JXom0Qz)

11\. The Metamask extension will appear again. Connect it by clicking the ‘Next’ button.

![](/files/7DevimPWYOSWtLK7Nt7G)

12\. You can start staking once Metamask is connected. Click the ‘STAKE’ button to proceed.

![](/files/ABRmbDKnsCvSr82DGE7U)

13\. Input XHT amount to stake from your available amount.

![](/files/4wcNTj2jlI41BwGfUBy4)

14\. You can select staking duration. The longer you stake the more you are rewarded.

* 1W: Staking for 1 week will earn regular rewards.
* 1M: Staking for 1 month will earn bonus rewards on your earnings.
* 1Y: Staking for 1 year will earn the highest bonus rewards on your earnings.

![](/files/RolODeQxbd9XSFSRlWL8)

15\. Check and confirm the details before you stake. These details include duration, predicted earnings, and slashing(early unstake). The duration is measured by the timing of the Ethereum blocks.

{% hint style="info" %}
Note that unstaking early will result in a percentage of your stakes principle to be deducted and earnings forfeited.
{% endhint %}

![](/files/k6nL4EcazxmJvQf33jzz)

16\. Click the ‘con rm’ button and complete your staking process.

{% hint style="info" %}
Note that enough Ethereum balance for the gas fee is needed to move forward with staking.
{% endhint %}

![](/files/fvVCEpCOShRO5xSuAHw6)

17\. The staking status will be displayed below after being done with staking. You can unstake early by clicking the button on the right. Once again, remember that unstaking early will result in a percentage of your stakes principle being deducted and earnings forfeited.

![](/files/u0yziA2KEjcbNZUidV6D)


# Travel Rule

The Travel Rule feature helps to keep transactions on your exchange compliant with required regulations, and thus, assist with obtaining licensing.

## What is the 'Travel Rule'?

The **Travel Rule** is a compliance feature that collects information about who is sending and receiving funds on larger transactions. It exists so your exchange can meet the regulatory *Travel Rule* requirement (originally FATF Recommendation 16), which asks Virtual Asset Service Providers (VASPs) to gather and keep basic counterparty details on transfers above a set value.

This guide explains what the feature does, what your users will experience, and how you, as an operator, manage it. It is intentionally non-technical.

### Travel Rule Feature Summary

When the Travel Rule is switched on, you set a *'threshold amount'*. Any deposit or withdrawal with a value **at or above** that amount must carry extra information about the other party (the '*counterparty*') and the reason for the transfer.&#x20;

Withdrawals are paused until the user fills in these details, and deposits are placed on hold until the source of funds is provided.&#x20;

Everything collected is stored as a compliance record you can look up later, one record per transaction.

***

## How to Set Up the Travel Rule Feature

Navigate to the *Travel Rule* in ***Operator Controls → General → Travel Rule***.

There are only two settings:

| Setting              | What it does                                                                                                                       |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **On / Off switch**  | Enables or disables the whole feature. When off, nothing changes for users, and no information is collected.                       |
| **Threshold amount** | The value, expressed in your exchange's **native currency**, at or above which a transaction must collect Travel Rule information. |

A few things worth knowing:

* The threshold is always measured in your exchange's **native currency** (for example, USDT). If a user transacts in another asset, the system converts that transaction's value into the native currency using live pricing, then compares it against your threshold.
* The check is *'at or above'* the threshold. A transaction exactly equal to the threshold qualifies; anything below it is unaffected.
* If the system cannot determine a transaction's value in native currency (for example, pricing is temporarily unavailable), it **does not** block or hold that transaction — normal operation continues. The Travel Rule never gets in the way when the value is unknown.

***

## Travel Rule User Flow

### Withdrawals

When a user requests a qualifying withdrawal, they are shown a short form **before the withdrawal can proceed**. They cannot complete the withdrawal until it is filled in.

The form asks, in order:

1. *Who is the service provider on the other side?*
   * **An exchange / VASP** - Another exchange or regulated service. The user then enters:
     * The name of that exchange/ provider&#x20;
     * The account holder's name.
   * **A self-custody wallet** - A private wallet. The user ticks *"The receiver is myself"* if it's their own wallet, or else, enters the counterparty's name.
2. *What is the purpose of the transfer?*&#x20;
   1. A fixed list is offered: Personal transfer, Investment, Purchase of goods/services, Salary/income, Gift, Trading, or Other (which lets the user type their own reason).

Once submitted, the withdrawal continues as normal, and a compliance record is saved.

### Deposits

Deposits work the other way around because the funds arrive on their own. When a qualifying deposit comes in, it is placed *on hold* instead of being credited immediately.&#x20;

The user is asked to provide the same kind of information as above, using the same form (worded from the sender's side).

Once the user provides the source-of-funds details, the deposit's Travel Rule hold is cleared and, assuming nothing else is holding it, the deposit is credited.

***

## The On-Hold System

The Travel Rule does not work in isolation. It plugs into a single, shared **hold system** that decides whether an incoming deposit is credited or kept pending.&#x20;

This is the most important concept for operators to understand because it is what ties the Travel Rule together with your other compliance controls.

The diagram below shows the journey of an incoming deposit through the hold system. The key takeaway is the loop near the bottom: the deposit keeps waiting until **every** active hold from any control has been cleared.

<details>

<summary>On-Hold Flow Chart</summary>

```mermaid
flowchart TD
    A([Deposit arrives]) --> B{From a blacklisted<br/>address?}
    B -- Yes --> BL[Hold: Blacklist<br/>no Travel Rule record created]
    B -- No --> C{At/above the<br/>Travel Rule threshold?}

    C -- No --> H
    C -- Yes --> TR[Hold: Travel Rule<br/>ask user for source of funds]

    subgraph SCREEN [Other controls may also add holds]
        K[Hold: KYT / screening<br/>flagged by AMLBot / Scorechain]
        AD[Hold: auto-deposit off<br/>or screening unavailable]
    end

    C --> SCREEN
    TR --> H{Any active<br/>hold reasons?}
    K --> H
    AD --> H
    BL --> H

    H -- "Yes — still held" --> W[Deposit stays pending]
    W -.->|"User submits Travel Rule info<br/>(clears Travel Rule only)"| H
    W -.->|"Plugin returns clean<br/>(clears KYT)"| H
    W -.->|"Operator clears blacklist /<br/>other holds"| H

    H -- "No holds left" --> CR([Deposit credited])
```

</details>

### Potential Hold Reason

A deposit can be held for more than one reason at the same time. The Travel Rule is just one reason among several. Common hold reasons include:

| Hold reason                                  | Where it comes from                                    | Typical meaning                                                           |
| -------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- |
| **Travel Rule**                              | The exchange (this feature)                            | The deposit is at/above your threshold and needs source-of-funds details. |
| **Blacklist**                                | The exchange                                           | The source address is on your blacklist.                                  |
| **KYT / screening**                          | A connected screening plugin (e.g. AMLBot, Scorechain) | The transaction was flagged by an automated risk check.                   |
| **Screening unavailable / auto-deposit off** | The platform                                           | Screening could not run, or you have manual deposit approval switched on. |

Each reason is tracked **independently** on the deposit. One can be cleared without disturbing the others.

### When a Hold is Credited

A held deposit is credited only when there are no active hold reasons left.

That means clearing the Travel Rule hold on its own is **not** enough if a screening hold is also active. The deposit remains pending until that one is resolved too.&#x20;

This is deliberate: it guarantees that no single control can be bypassed by satisfying a different one. A deposit has to pass *all* of your active checks before the funds become spendable.

#### Blacklisted addresses

A blacklist match is treated as a hard stop and **takes priority**. When a deposit arrives from a blacklisted address:

* The deposit is held on the blacklist reason.
* No Travel Rule record is created.&#x20;
  * There is no point asking the user for counterparty details on funds you are rejecting on other grounds, so the user is never prompted for further information, and you are not left with a half-collected record for a transaction you intend to reject.

#### KYT, AML, and Transation Screening Plugins

If you run a Know-Your-Transaction (KYT) screening plugin such as [AMLBot](/plugins/use-plugins/amlbot-crypto-risk-screening) or [Scorechain](/plugins/use-plugins/scorechain-crypto-risk-screening), it participates in the very same hold system:

* When a screening plugin *flags* a deposit, it adds its own KYT hold reason.&#x20;
  * That hold coexists with any Travel Rule hold on the same deposit — both must be cleared before the deposit is credited.
* When a screening plugin returns a *clean* result, it clears the holds it is responsible for, allowing automated processing to continue.

Because every control writes to one shared hold list, a deposit can be simultaneously waiting on the user (Travel Rule) and on your screening provider (KYT) — and it will sit pending until both are satisfied. You don't have to coordinate these controls manually; they layer on top of one another automatically.

### Clearing Responsibilty

The ability to clear a hold is split by responsibility:

| Hold reason                   | Who can clear it                                                                                                |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
| **Travel Rule**               | The **user** clears it by submitting the requested source-of-funds information. (Operators can also act on it.) |
| **KYT / screening**           | Cleared automatically by the **screening plugin** when it returns a clean result, or by the operator.           |
| **Blacklist and other holds** | The **operator**, from the admin transaction screens.                                                           |

The important guardrail: when a **user** submits their Travel Rule details, that action clears **only** the Travel Rule reason. It can never clear a blacklist, screening, or other operator-managed hold. Users can resolve the one thing they are responsible for (their own counterparty details)  while every genuine risk decision stays firmly with the operator and your screening tools.

***

### How This Assists the Operator

* **One place, one rule**
  * Travel Rule, blacklist, and KYT screening all feed the same hold system, and a deposit is only released when every check passes. You don't maintain separate, competing approval flows as they stack automatically.
* **No accidental release**&#x20;
  * Because a deposit needs *all* active holds cleared, satisfying one control can never unlock funds that another control is still flagging.
* **Less manual work**
  * Users resolve their own Travel Rule prompts and screening plugins clear their own flags automatically, so routine cases move forward without you touching them. You step in only for the holds that genuinely require an operator decision.
* **A clean audit trail**
  * Every qualifying transaction leaves a compliance record you can pull up per transaction, which makes responding to regulators or information requests straightforward.
* **Sensible prioritisation**
  * Hard rejections (blacklist) short-circuit the rest, so you're never collecting or reviewing counterparty details for funds you were always going to reject.

***

## How to Review Records as the Operator

When the Travel Rule feature is active, a *View travel rule details* link appears on every deposit and withdrawal row in the admin transaction screens, as well as under an individual user's profile.

Clicking it fetches and shows the compliance record collected for that transaction: counterparty type, the names provided, and the purpose of the transfer.&#x20;

If a particular transaction never required Travel Rule information (for example, if it was below the threshold), the panel simply reports that no Travel Rule record was collected for it.

Each qualifying transaction is its own record — there is no merging or reuse of details across transactions, even when the same wallet address is involved. The user is asked every time, so every record reflects the information as it was at the time of that specific transfer.

### What gets stored

For each qualifying transaction, the record keeps:

* Whether it was a **deposit** or **withdrawal**, and the transaction itself.
* The **transaction value**, both in the original asset and in your native currency.
* The **counterparty type** (exchange/VASP or self-custody).
* The **counterparty name** and, for exchanges, the **service-provider (VASP) name**.
* Whether the user declared the wallet as **their own**.
* The **purpose** of the transfer.

These records are retained so you can demonstrate compliance and respond to information requests.


# HollaEx Plugins

Plugins offer even more personalized functionality for your exchange, and can be added and removed with ease.

HollaEx provides a variety of software add-ons as plugins. These are ready for all operators to add with ease.&#x20;

To browse and add new plugins, head to the *Operators Controls* from an Admin account, then to Plugins in the sidebar.

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

***

## Current HollaEx Plugins

### Fiat Ramping :moneybag:

* [HollaEx Bank](/plugins/use-plugins/bank)
* [Guardarian](/plugins/use-plugins/guardarian-fiat-on-ramp)
* [Transak](/plugins/use-plugins/transak-fiat-on-off-ramp)

### KYC/ KYB :bust\_in\_silhouette:

* [HollaEx Manaul KYC](/plugins/use-plugins/kyc)
* [iDenfy](/plugins/use-plugins/idenfy-automatic-kyc)
* [Sumsub](/plugins/use-plugins/sumsub-automatic-kyc)
* [Persona](/plugins/use-plugins/persona-automatic-kyc)
* [Facevault](/plugins/use-plugins/facevault-automatic-kyc)
* [Shufti Pro (KYC and AML)](/plugins/use-plugins/shufti-pro-kyc-and-aml-verification)

### Risk Screening (KYT) :detective:

* [Scorechain](/plugins/use-plugins/scorechain-crypto-risk-screening)
* [AMLBot](/plugins/use-plugins/amlbot-crypto-risk-screening)
* [TRM Labs](/plugins/use-plugins/trm-labs-wallet-and-transaction-risk-screening)

### SMS :mobile\_phone:

* [Telynx](/plugins/use-plugins/telynx-sms-notifications-and-verifications)
* [Sinch](/plugins/use-plugins/sinch-sms-notifications-and-verifications)
* [AWS SMS](/plugins/use-plugins/aws-sns-sms-messaging)
* [Messente](/plugins/use-plugins/messente-sms-service-integration)

### Custody :closed\_lock\_with\_key:

* [Cobo](/plugins/use-plugins/cobo-wallet-custody-integration-waas-2.0)
* [Hextrust](/plugins/use-plugins/hextrust-wallet-custody-integration)

### Support :information\_source:

* [Crisp](broken://pages/O9M5p5tqhze1zJq400Lj)
* [Intercom](broken://pages/H0wo7ta9Fz20jjYGgJCn)

### Other

* [CoinMarketCap](/plugins/use-plugins/coinmarketcap-market-data-integration) - Creates a set of API endpoints, making integration with Coin Market Cap easier

***

In addition to the ready-made plugins developed either by HollaEx or approved by a trusted community developer, all operators can create their own plugins for their exchange and potentially have them listed in the plugin store for other operators to use.

The process of doing this can be found here:

{% content-ref url="/pages/-MPI5GZ4MLT7JKwEcACN" %}
[Developing Plugins](/plugins/develop-plugins)
{% endcontent-ref %}


# HollaEx Bank - Fiat Withdrawals

The Bank plugin is used to store users' banking details making it easier to allow users to withdraw funds

{% hint style="info" %}
This Bank plugin is of use to exchanges that use the Fiat Controls method of fiat ramping.&#x20;

If your exchange uses a third-party payment provider only, such as with the Banxa or Guardarian plugins, this plugin may not be required.&#x20;
{% endhint %}

## What Is It?

The Bank plugin stores the banking information of your exchange's users, allowing for interaction with Fiat Controls.&#x20;

<figure><img src="/files/mVW2LqXziEvJkS22VrVY" alt=""><figcaption><p>An example of a user's bank section in their user profile. Multiple banks may be stored and the particlaur data can be edited</p></figcaption></figure>

## Who Needs It?

Operators who choose to use the *Fiat Controls* method (as opposed to using a third-party on/ off-ramp such as Banxa) would benefit from storing the banking information of their users' banking details so they can deposit fiat to a user's chosen bank account.

## How to Use It?

On installation of the Bank plugin, we will be greeted by the configuration page. This will allow the setting of required data fields. The fields that will be required depend on what system your users are using for banking.&#x20;

*For example, exchanges with Indian users may want to ensure that the IFSC  number is required.*

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

The fields are:

* IBAN - *International*
* Note - *Generic*
* Bank Name - *Generic*
* IFSC Number - Indian Financial System Code (India)
* ABA Number - American Bankers Association Routing Number (USA)
* BSB Number - Bank State Branch (*Australia*)
* Swift Code - *International*&#x20;
* Card Number - *Generic*&#x20;
* Account Name - *Generic*
* Acquirer Bank - *Generic*&#x20;
* Account Number (Default set to True) - *Generic*
* Routing Number (Sort Code UK) - *Generic*

Do note that these fields are not used to automatically send withdrawals. Instead, the bank plugin offers the relevant information of users for operators to then arrange to process withdrawals.


# Guardarian - Fiat On-Ramp

Guardarian offers a quick and simple on-ramp for fiat enabled exchanges to add, and offer their users

{% hint style="info" %}
Must have an account with Guaradarin and have completed their onboarding. Get in touch by messaging their sales team on [this page](https://guardarian.com/integrate-us), and let them know you are running a HollaEx exchange.
{% endhint %}

## What Is It?

Guardarian is a plugin that allows users to on-ramp their fiat and has it converted into crypto. Guardarian covers a wide selection of both fiat and crypto and offers a variety of ways to pay depending on the fiat currency chosen, such as Revolut or Google Pay.&#x20;

<figure><img src="/files/hVmlPdnrnMj1FsQnV50d" alt=""><figcaption><p>The Guardian widget</p></figcaption></figure>

## Who Needs It?

Operators who want to offer Fiat ramping to their users. Guardarian in particular supports quite an extensive list of fiat currencies including some smaller ones, which could be useful for operators in certain locales.

## How to Use It?

Simply install from the plugin store and access the configuration page via the green *configure* button. On this page, two strings are required:

* **Public Guardarian URL** - This is the public URL to access Guaradarian's service.
* **Private API Key** - This key will be provided on signing up with Guaradarin

Both URL and private key are required for this plugin to function.

With these added, the Guardarian page that has been added to your exchange will work.&#x20;

It can accessed from both the sidebar and the nav bar, by default titled 'Credit Card' however this can be easily [changed](/how-tos/customize-exchange).&#x20;

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

On the Guardarian page, the widget is nice and simple, user select what fiat they want to convert, from, and what crypto asset to convert to. The user inputs the amount they want and they will be taken outside the exchange to a branded page where they can complete the transaction.

<figure><img src="/files/rQ35AXO6iTmHkCqjG8vn" alt=""><figcaption><p>Guaradarin payment screen</p></figcaption></figure>

If this is the user's first visit to Guardarian, they will be asked to check their crypto address, verify their email, and then complete the transaction.&#x20;

Once the transaction is complete, after a small amount of time, the chosen crypto asset will be deposited in the user's account.&#x20;


# Transak - Fiat On/ Off Ramp

## Transak - Fiat On/Off Ramp

## Available To

Free for the following exchange plans:

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **Transak Plugin** connects a HollaEx exchange to [Transak](https://transak.com) so users can **buy** and **sell** crypto with cards, bank transfers, and local payment methods directly from inside the exchange interface.

The plugin adds a `Buy/Sell crypto` page at `/buy-crypto` (rendered by the bundled web view) and exposes the server-side endpoints that the page needs to:

* Discover which fiat currencies, cryptocurrencies, and payment methods Transak currently supports for your account.
* Build a live quote (`POST /plugins/transak/estimate`) using the Transak pricing API.
* Open a fully hosted Transak widget session (`POST /plugins/transak/transaction`) — pre-filled with the user's account, network, deposit address (for BUY), wallet redirection (for SELL), and a partner order id, so the rest of the flow happens on Transak's checkout.
* Show the user the status of their recent Transak orders (`GET /plugins/transak/status`) by querying Transak with the user's `network_id` and wallet addresses.

KYC, payment processing, fraud checks, fiat settlement, and crypto delivery are all handled by Transak. The exchange only needs to provide the user identity, the deposit address (BUY) or sell origin (SELL), and the redirect URL.

## Who Needs It?

This plugin is suitable for any HollaEx exchange operator that:

* Wants to offer card and bank transfer crypto purchases without becoming a regulated fiat processor.
* Wants to give users a one-click off-ramp (sell crypto to fiat) that lands money in the user's bank account or card.
* Needs broad coverage of regional payment methods (SEPA, UPI, PIX, Apple Pay, Google Pay, debit/credit cards, local bank transfers, etc.).
* Wants to keep the buy/sell experience inside the exchange UI (the page is registered at `/buy-crypto` and shows up in the appbar/sidebar/menu by default).

## How to Use It?

Install the plugin from the **Plugins** section inside the Operator Control, then configure it with your Transak partner credentials and the environment you want to run against.

#### 1. Get your Transak partner credentials

1. Apply for a partner account on the [Transak Partner Portal](https://partners.transak.com).
2. Once approved, open **Settings → API Keys** and generate:
   * **API Key** — sent on every public pricing/quote request as `partnerApiKey` and used to load the widget.
   * **Access Token** — sent as the `access-token` header on the partner-authenticated endpoints (widget session creation and order lookup).
3. Pick which environment you want to integrate against first. Transak provides both:

   * **Staging** — `https://global-stg.transak.com`, `https://api-stg.transak.com`, `https://api-gateway-stg.transak.com`.
   * **Production** — `https://global.transak.com`, `https://api.transak.com`, `https://api-gateway.transak.com`.

   The plugin ships with **staging** values by default, so you can test before going live. The credentials issued for staging are different from production credentials — make sure the API key/access token you paste in matches the environment the URLs are pointing at.

#### 2. Whitelist your exchange domain on Transak

Transak validates the `referrerDomain` of every widget session. In the Transak Partner Portal, add your exchange's hostname (e.g. `exchange.example.com`) to the **Allowed Domains** list for both staging and production. Without this, widget sessions will be created, but Transak will refuse to render the checkout.

#### 3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                          | Required | Default                               | Description                                                                                                                                                                                                    |
| ------------------------------ | -------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transak_environment` (public) | **yes**  | `STAGING`                             | Environment identifier — `STAGING` or `PRODUCTION`. Used by the plugin to derive the correct fallback URLs and by the widget to label the session.                                                             |
| `transak_api_url` (public)     | **yes**  | `https://api-stg.transak.com`         | Transak public API base URL (used for fiat list, crypto list, and pricing). Use `https://api.transak.com` for production.                                                                                      |
| `transak_gateway_url` (public) | **yes**  | `https://api-gateway-stg.transak.com` | Transak gateway API base URL used to create authenticated widget sessions. Use `https://api-gateway.transak.com` for production.                                                                               |
| `transak_widget_url` (public)  | **yes**  | `https://global-stg.transak.com`      | Base URL for the Transak hosted widget. Use `https://global.transak.com` for production.                                                                                                                       |
| `api_key`                      | **yes**  | —                                     | Transak partner API key (sent as `partnerApiKey` and used to load the widget).                                                                                                                                 |
| `access_token`                 | **yes**  | —                                     | Transak partner access token (sent as the `access-token` header for session creation and order lookup). Keep this secret — it lets the bearer create widget sessions and read orders for your partner account. |

The plugin's `init()` will throw if any of these values are missing, retrying once a minute until they are set, so it is safe to install the plugin first and fill in the credentials afterwards.

#### 4. (Optional) Customize the user-facing page

The bundled web view registers a page at:

```
https://<your exchange url>/buy-crypto
```

Defaults set in the manifest:

* `is_page: true`, `is_public: true` — Anyone can land on the page; the plugin endpoints themselves still require a user bearer token.
* `hide_from_appbar`, `hide_from_sidebar`, `hide_from_menulist`, `hide_from_bottom_nav` are all `false`, so the entry point is visible in every navigation surface by default. Flip any of them to `true` from the plugin meta to remove the entry from that surface.
* The page title (`"Buy/Sell crypto"`) and disclaimer copy live in `web_view[0].meta.strings.en` — Edit those values on the plugin to relabel the page or swap the disclaimer wording per market.

#### 5. Endpoints exposed by the plugin

All endpoints below are mounted under `https://<your exchange url>/plugins/transak/`. Calls that touch user data require the standard HollaEx user bearer token.

| Method | Path                                                           | Auth | Purpose                                                                                                                                                                                                                                                                                    |
| ------ | -------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `GET`  | `/currencies`                                                  | none | Lists fiat and crypto symbols Transak supports for your partner account *and* are present on the exchange. Cached for 1 hour.                                                                                                                                                              |
| `GET`  | `/market_pair?pair=<fiat>_<crypto>&transaction_type=buy\|sell` | user | Returns the resolved Transak network and the active payment methods (with min/max limits) for that pair and direction.                                                                                                                                                                     |
| `POST` | `/estimate`                                                    | user | Builds a Transak quote without creating a session. Body: `fiat_currency`, `digital_currency`, `requested_amount`, `payment_method_id?`, `transaction_type`.                                                                                                                                |
| `POST` | `/transaction`                                                 | user | Creates a Transak widget session for the requested order, returns a `widget_url` the front-end can open in an iframe / new tab. Falls back to the public widget URL with `apiKey` if the gateway session call fails. Body: same fields as `/estimate` plus a required `payment_method_id`. |
| `GET`  | `/status`                                                      | user | Returns the user's last 90 days of Transak orders (matched by `partnerCustomerId = user.network_id` and by the user's wallet addresses).                                                                                                                                                   |

#### 6. How a buy or sell flows

When the user submits the form on `/buy-crypto`:

1. `POST /plugins/transak/transaction` is called with the chosen pair, amount, payment method, and direction.
2. The plugin resolves the right Transak crypto record (preferring the network the kit already configures for that coin), generates a fresh `partnerOrderId`, and looks up — or creates — the user's wallet address for that currency/ network (BUY only).
3. It calls `POST <gateway>/api/v2/auth/session` with the partner access token to mint a **signed widget session**, which keeps the API key off the front-end. If the gateway is unreachable for any reason, it falls back to a plain widget URL with the `apiKey` query parameter.
4. The user is redirected to the hosted Transak widget, where KYC, payment, and crypto delivery happen. On completion, Transak redirects back to your exchange URL.
5. The exchange UI shows live order status by polling `GET /plugins/transak/status`.

#### Supported networks

Network identifiers between HollaEx and Transak are mapped automatically with aliases for the common chains:

* Ethereum (ERC20)
* Tron (TRC20)
* Bitcoin
* BNB Smart Chain (BEP20)
* Polygon
* Arbitrum
* Optimism
* Avalanche C-Chain
* Solana
* Litecoin
* Dogecoin
* Ripple (XRP)
* Stellar (XLM)

If a coin exists on Transak under a network that is not in the alias list, the plugin will still try a best-effort name match. Coins Transak does not list at all (or that the kit does not have configured) are filtered out of `/currencies`.

***

**Benefits for HollaEx Operators**

The Transak plugin lets exchange operators offer fiat-to-crypto and crypto-to-fiat without holding a payment license, building card processing, or integrating each regional payment method individually. Users get a familiar checkout (cards, bank transfers, local rails, mobile wallets) inside the exchange UI; the exchange gets a deposit address pre-filled buy flow, an automatic sell flow that pays the user out to their bank, and a partner-authenticated session model that keeps the API key off the front-end. Switching between staging and production is a single environment + URL change in the plugin meta.


# Banxa - Fiat On-Ramp

{% hint style="info" %}
Must have an account with Banxa and have completed their onboarding. Get in touch with the <support@hollaex.com> team, who will assist you in connecting with Banxa.

In addition Banxa will require at $15,000 set up free to them, to get started.
{% endhint %}

## What Is It?

Banxa is a plugin that allows users to on-ramp their fiat and have it converted into crypto. Banza supports a wide range of fiat and crypto and offers a variety of payment methods depending on the chosen fiat currency.

<figure><img src="/files/hVmlPdnrnMj1FsQnV50d" alt=""><figcaption><p>The Guardian widget</p></figcaption></figure>

## Who Needs It?

Operators who want to offer Fiat ramping to their users. Banxa is one of the largest fiat ramps in the business and offers a vast range of both fiat and crypto assets.

## How to Use It?


# HollaEx KYC

KYC is an invaluable tool for ensuring your customers are who they say they are, and remaining compliant with your jurisdiction's laws.

## What Is It?

The HollaEx KYC plugin allows operators to run their own KYC process. Users upload their documents to a secure AWS S3 bucket, as well as give information on themselves, and then the operator (or KYC-enabled member of staff) can verify the user according to your exchange's particular policy.

## Who Needs It?

Operators who want to KYC their users, without having to use a third-party service.

## How to Use It?

{% hint style="info" %}
Please refer to the [AWS S3 Bucket Setup & AWS IAM Access Key Setup](#aws-resources) sections first, before proceeding.
{% endhint %}

After installing the KYC plugin, four fields need to be filled in before the plugin can be used. These credentials require setting up an account on the [AWS Console and creating an S3 Bucket](/plugins/use-plugins/aws-sns-sms-messaging#aws-access-key-generation). This bucket is the location where uploaded documents will be stored.

<figure><img src="/files/dIIrmRRdVQWJw3otaQ8P" alt=""><figcaption><p>Completed Configure page</p></figcaption></figure>

With these details filled out. Hit the *Save* button. We can now see what the user experience is.&#x20;

***

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

Now users can select *Identification* from the sidebar and enter the identification process by selecting *Identity* and clicking *Start Identity Verification.*

The first page- *Identity*- is a simple form where basic details like name, DOB, address, etc. are supplied and submitted.

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

The second page, *Upload,* has three sections in it, where document scans are uploaded to the earlier setup AWS S3 Bucket:

* Passport scan&#x20;
* Proof of Residence
* Selfie

{% hint style="info" %}
As this method of KYC is run entirely by your team, this is where an operator-defined policy should be put into place. As the verification is done by hand, there is no hard limit in place for what will be accepted by the software.
{% endhint %}

Once complete, the user will be able to see that their documents have been submitted

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

At this point, the users can get in touch with the support service for their exchange. A member of the team can search for the user's [account in your operator controls](/how-tos/operator-control-panel), find that particular user, and then view and accept or reject their documents. This can be seen in the image below:

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

On approval, the user will be considered verified and see a green check beside *Identity* in the *Identification* section. At this point, your policy can dictate what rewards or account limits this opens up to them.

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

## AWS Resources

It is required to create a new AWS S3 bucket and an IAM user to connect to the HollaEx KYC plugin. The plugin will store the user's KYC data in the bucket as storage.

Please follow the guide below to create the resources.

### Automated resource creation

{% hint style="info" %}
You can apply a Cloudformation template to automate the S3 bucket & IAM account creation with a simple click and a few types. Please click the link below to proceed.\
\
The AWS Web console will pop up through the link. Please make sure to log in with your existing AWS account.\
\
[🔗 Cloudformation Template ](https://console.aws.amazon.com/cloudformation/home?region=eu-west-3#/stacks/quickcreate?templateURL=https://hollaex-plugins-cloudformaiton.s3.ap-southeast-1.amazonaws.com/cloudformation.yaml\&stackName=hollaex-kyc-s3)
{% endhint %}

{% hint style="info" %}
![](/files/wy5Qh9N2HP5357Dcz61x)\
\
It is important to set the AWS Region through the top bar before you proceed. The new S3 bucket will be created in the region that you specified at this level.
{% endhint %}

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

Once you click the link, you'll see a page to set multiple parameters. Here are a few important ones:

* **Stack name**: The name of the Cloudformation stack. It is okay to leave it as the default in most cases.
* **BucketNamePrefix**: The name of the new AWS S3 bucket that you are going to create. The system will add a unique account ID after the provided bucket prefix as a final bucket name. For example: `<myexchange>-s3-kyc`(Change myexchange with your unique exchange name)&#x20;
* **MyIAMUsername:** The name of the new AWS IAM username that you are going to create. This will be used as a name for the IAM user with AWS S3 access. For example: `kyc-iam-user`

It is not needed to set the "Permissions - optional" panel in most cases. You can simply skip.

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

It will take a few minutes for the system to create the resources.

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

Once it's all completed, you'll be able to check the output. You'll get everything necessary, including the IAM Access Key and the Secret Key. Please save this information somewhere safe, and refer to the [docs](#how-to-use-it) to configure the plugin. You are all good now!

### Manual resource creation

{% hint style="info" %}
It is recommended to use the Cloudformation automated template above instead of going through the manual setup. Please go through the manual setup only if you know what you are doing.
{% endhint %}

#### AWS S3 Bucket Setup

<figure><img src="/files/0XiMbEKQAcXoLXOdCHSn" alt=""><figcaption></figcaption></figure>

Please create a new AWS S3 bucket to use for the HollaEx KYC. You can do it from the ["Buckets" tab of the AWS S3 page.](https://ap-southeast-1.console.aws.amazon.com/s3/home)

After the creation, make sure **not** to completely block public access to your AWS S3 bucket. It is required for HollaEx Web to reach an S3 bucket without any permission issues. Please refer to the screenshot below.&#x20;

You can find it in the "Permissions" tab of your bucket.

<figure><img src="/files/6EfowQO9T7OPkJDAzTuH" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/0EycXdqQFL7FYjdEHPbl" alt=""><figcaption></figcaption></figure>

#### AWS IAM Access Key Setup

You will need to make an IAM policy for an IAM user you are going to use for the KYC. You should create a new policy on the [*AWS IAM Console*](https://us-east-1.console.aws.amazon.com/iam/home) *- Access Management - Policies* page, as shown in the image and code block below.&#x20;

<figure><img src="https://lh5.googleusercontent.com/dKLF4TDdrBrqaBK4uMIVFUeylZs1qy3qdqqGNisW1m1uFzYTPibuuB4VL-fptH0WrMZIQBTLyShU0ZjVaamKOuMwCyhegD2ttuKLF-6I6nPdHYqHa7Ky-fL3D_KkstiX6tRhhHFH9adbiP1OdDWZMM0" alt=""><figcaption></figcaption></figure>

```
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::my-bucket-name/*"
}
]
}

```

After this, go to the *AWS IAM Console - Access management - Users* page to create a new user for the KYC connection. You will be able to bind the policy that you just created above to this new account.

<figure><img src="https://lh3.googleusercontent.com/xM9ZZ9GCP0Ha_v1ElcanDJICNUsMf_C36zeQa7pmfhtTPvFV9d5RfhDTbOVTJG-Z03JyC8P9JM6EXPZU00ZjOmZGc4PW3XpizLSko8lzTmPiIUvVV3wxdYcZcICknuzMs1GmEUUetuRy0WNk4yF66bA" alt=""><figcaption></figcaption></figure>

Once the account is created, get the required Access Key ID and Secret Access Key by clicking the "Download .csv" button.

<figure><img src="https://lh6.googleusercontent.com/qpOEk3JqCw39ur-u-emhPlv1xi_CJII4xgLUNLXcMj2evDE8L-djTS7MmF0Gjw8mTFRyUzDFtttGyCb50rkCk1vggSH-EALmIvBeyw-Qbdq1u8NXp00WKt7860QQEmteZEhWjR0udt3BAinPmSNgZps" alt=""><figcaption></figcaption></figure>


# iDenfy - Automatic KYC

iDenfy is a leading KYC operator that can be integrated easily into your exchange

Includes 100 iDenfy credits (no iDenfy setup required), with a cost of around $2 per verification after this. Contact HollaEx support to purchase more credits.

{% hint style="info" icon="sack-dollar" %}
Setup costs with iDenfy are in the 5 figures, but luckily, with your HollaEx plan, you can avoid this setup fee and just have to pay for the credits themselves!
{% endhint %}

## What Is It?

The HollaEx KYC plugin allows operators to run their own KYC process. Users upload their documents to a secure AWS S3 bucket, as well as give information on themselves, and then the operator (or KYC-enabled member of staff) can verify the user according to your exchange's particular policy.

The Automatic KYC plugin is a seamless way to verify the identity of your users. This plugin allows users to upload their identification documents easily through iDenfy. The KYC verification process is secure and efficient, ensuring a smooth user experience.

To get started with the Automatic KYC plugin, simply install it from the exchange app store. Fiat Ramp and Boost plan subscribers can use this plugin for free. If you're not a subscriber, you can purchase the plugin by clicking the green ‘buy’ button and proceeding with the payment.

After installing the Automatic KYC plugin, you'll need to configure the following items.

**Public**

* `error_pathPath`: This is the path where a user will be redirected after a failed identification.
* `success_pathPath`: This is the path where a user will be redirected after successful identification.
* `utility_bill`: This determines whether the user is required to attach a utility bill when uploading documents.
* `unverified_path`: This is the path where a user will be redirected after a not analyzed identification (e.g., user immediately cancels the process).

**Private**

* `manual_review`: This determines whether the document requires a final manual review by the admin.

Once it’s all done, you can see that a new section has been added to your exchange verification page. This section is used for KYC/AML verification of your users.

Additionally, the ‘manually upgrade’ feature allows you to update the plugin to a newer version by uploading a .json file. This will preserve the current plugin's configuration values, ensuring that your settings are not lost during the upgrade process.


# Sumsub - Automatic KYC

Sumsub is an industry leading KYC provider, popular worldwide

## Available To

Free for the following exchange plans:&#x20;

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **Sumsub Plugin** is an identity verification and compliance solution integrated into HollaEx exchanges. Powered by Sumsub (*Sum & Substance*), it provides KYC (Know Your Customer), KYB (Know Your Business), and AML (Anti-Money Laundering) checks. With support for ID verification, liveness detection, proof of address, and more, it ensures that exchanges remain compliant while maintaining a fast and user-friendly onboarding process.

## Who Needs It?

This plugin is essential for any HollaEx exchange operator that:

* Needs to meet regulatory KYC/AML requirements.
* Wants to automate and streamline customer onboarding.
* Operates in regions where compliance is mandatory for trading, deposits, or withdrawals.
* Aims to reduce fraud while building trust and security with its user base.

## How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, input your **Sumsub API key**.

You will also need to set up a webhook in Sumsub:

1. Go to [Sumsub Cockpit](https://cockpit.sumsub.com).
2. Navigate to **Dev Space → Webhooks → Webhook Manager**.
3. Create a new webhook.
4. Set the **Target URL** to:

   ```
   https://<your exchange url>/api/plugins/sumsub/webhook
   ```
5. Leave the rest of the settings at their default values.

Once configured, all verification requests and responses will be automatically managed between HollaEx and Sumsub.

***

#### Benefits for HollaEx Operators

The Sumsub plugin saves exchange operators time and resources by outsourcing identity checks to a trusted global compliance provider. It helps reduce fraud, prevents unauthorized access, and ensures that exchanges are legally compliant in multiple regions. This allows operators to focus on growing their business, while Sumsub handles the heavy lifting of identity verification and regulatory compliance.


# Persona - Automatic KYC

## Available To

Free for the following exchange plans:&#x20;

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **Persona Plugin** is an identity verification and KYC solution integrated into HollaEx exchanges. Powered by [Persona](https://withpersona.com), it provides a fully hosted identity verification flow — government ID, selfie, liveness, and document checks — without your team having to handle sensitive PII directly.

The plugin creates a Persona inquiry per user, redirects the user into the hosted Persona flow, listens for inquiry status updates via a secure webhook (which is then re-verified against the Persona API to prevent spoofing), and updates the user's `id_data.status` on the exchange. Admins also have manual controls to verify or revoke a user's KYC status from the operator dashboard.

## Who Needs It?

This plugin is essential for any HollaEx exchange operator that:

* Needs to meet regulatory KYC/AML requirements.
* Wants to automate and streamline customer onboarding.
* Operates in regions where compliance is mandatory for trading, deposits, or withdrawals.
* Wants a customizable, white-labeled verification flow controlled from a single Persona template.

## How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, input your **Persona API key** and **template ID**.

#### 1. Get your Persona credentials

1. Log in to the [Persona Dashboard](https://app.withpersona.com).
2. Open **Settings → API Keys** and create an API key. It is sent on every request as a Bearer token.
3. Open **Inquiries → Templates**, create or select a template (the hosted flow your users will see), and copy its **Template ID** (`itmpl_XXXXXXXXXXXXXXXXXXXXXXXX`).

#### 2. Set up the webhook in Persona

1. In Persona, go to **Settings → Webhooks**.
2. Create a new webhook.
3. Set the **Target URL** to:

   ```
   https://<your exchange url>/api/plugins/persona/webhook
   ```
4. Subscribe to the `inquiry.*` event family (at a minimum `inquiry.approved`, `inquiry.declined`, `inquiry.failed`, `inquiry.expired`).

The plugin re-verifies every webhook against the Persona API before changing a user's status, so the webhook secret is not strictly required for security — but you can still set one in Persona for defense in depth.

#### 3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field          | Required | Default                          | Description                                                          |
| -------------- | -------- | -------------------------------- | -------------------------------------------------------------------- |
| `url` (public) | yes      | `https://withpersona.com/api/v1` | Persona API base URL.                                                |
| `api_key`      | **yes**  | —                                | Persona API key (Bearer token used for all API requests).            |
| `template_id`  | **yes**  | —                                | Persona inquiry template ID (e.g. `itmpl_XXXXXXXXXXXXXXXXXXXXXXXX`). |

Once configured, all verification requests and responses will be automatically managed between HollaEx and Persona.

***

#### Benefits for HollaEx Operators

The Persona plugin saves exchange operators time and resources by outsourcing identity checks to a trusted global compliance provider. It helps reduce fraud, prevents unauthorized access, and ensures that exchanges are legally compliant in multiple regions. The customizable Persona templates let you tailor the verification flow per market — collecting passport in one country, driver's license in another — while the plugin keeps the on-chain user state in lock-step with Persona's authoritative inquiry status.


# FaceVault - Automatic KYC

Facevault offers a means of completing KYC checks, with a custom plugin developed by the FaceVault team, offering KYC at a competitive rate

## FaceVault - Automatic KYC

The FaceVault Plugin is an identity verification solution integrated into HollaEx exchanges. Powered by [FaceVault](https://facevault.id), it provides a fully hosted verification flow — government ID capture, document OCR, selfie liveness, face matching, and anti-spoofing — without your team handling raw document images in the exchange UI.

The plugin adds an **Identity Verification** tab to the user dashboard. Users complete KYC on your branded FaceVault-hosted page (`facevault.id/v/<slug>`), and the tab shows their latest status (verified, under review, or failed) based on FaceVault’s trust score. The plugin is **webview-only** (no server-side install scripts), so it works on **HollaEx Cloud** and self-hosted kits.

For API details, trust scoring, and advanced options, see the [FaceVault documentation](https://facevault.id/docs).

FaceVault is fully open source, and can be viewed on it's [Github repo.](https://github.com/khreechari/facevault-hollaex)

***

### Who is this for?

This plugin is ideal for exchange operators who:

* Need to meet **KYC / AML** requirements for trading, deposits, or withdrawals.
* Want to **automate** identity checks instead of manually reviewing every passport and selfie in the built-in HollaEx KYC flow.
* Prefer a **hosted** verification experience with FaceVault branding (logo, accent, copy) on a dedicated page.
* Run on **HollaEx Cloud** or a **self-hosted** kit and want a third-party plugin without custom kit builds.

***

### How it works

1. A user opens **Identity Verification** in their account.
2. The plugin sends them to your FaceVault-hosted verification page for the configured **slug**.
3. FaceVault runs ID upload, optional proof of address (depending on site level), liveness, face match, and fraud checks.
4. FaceVault returns a **trust score** (0–100) and decision (`accept`, `review`, or `reject`).
5. The plugin updates the **KYC tab badge** in HollaEx to reflect the result.

Operators can still review borderline cases and manage users from the operator dashboard as with other external KYC plugins.

***

### Installation

You can install the plugin from the **Operator Control Panel** or by uploading the marketplace JSON file.

#### Option A — Pre-configured JSON from FaceVault (recommended)

1. Sign up at [devdash.facevault.id](https://devdash.facevault.id).
2. Open **Sites** and click **+ Add Site**.
3. Configure your hosted verification site (name, verification level, **slug**, branding, safety options). Hosted page is enabled by default.
4. On the site card, choose **HollaEx plugin…** → **Download**. The JSON includes your slug pre-filled.
5. In HollaEx, go to **Plugins** → **My plugins** → **Add Third Party Plugin**.
6. Paste or upload the JSON, then **Activate** the plugin.

No slug entry is required in HollaEx if you use the downloaded file.

#### Option B — Generic marketplace JSON

Use the App Store / marketplace file (for example `facevault-kyc.marketplace.json`):

1. In the Operator Control Panel, go to **Plugins** → **My plugins** → **Add Third Party Plugin**.
2. Paste the full JSON contents (or upload the file), then **Activate**.
3. Open **Plugins** → select **facevault-kyc** → **Configure**.
4. Set your **slug** (see configuration table below).

See also: [Installing plugins](https://docs.hollaex.com/plugins/installing-plugin).

***

### Configuration

#### 1. Create your FaceVault site and slug

If you have not already:

1. Sign in at [devdash.facevault.id](https://devdash.facevault.id).
2. Create a hosted-verification **Site** and choose a unique **slug** (for example `acme`). Slugs must be available and cannot impersonate major brands.
3. Optional: customize **Branding** (display name, accent, logo) and **Safety** (captcha, daily cap, fraud-disclaimer text) on the site.

Your public verification URL will be:

```
https://facevault.id/v/<slug>
```

#### 2. Configure the plugin in HollaEx

Open the plugin in the Operator Control Panel and set the following field:

| Field  | Required | Default | Description                                                                                                                                            |
| ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `slug` | yes      | —       | Your FaceVault hosted-page slug (for example `acme`). Create the site at [devdash.facevault.id](https://devdash.facevault.id) and paste the slug here. |

Plugin metadata (from marketplace JSON):

| Property    | Value                                  |
| ----------- | -------------------------------------- |
| Plugin name | `facevault-kyc`                        |
| Type        | `external_kyc`                         |
| Version     | `2.0.14` (marketplace version `20014`) |
| Author      | FaceVault                              |

Once the slug is saved and the plugin is enabled, users will see **Identity Verification** in the KYC area and can start verification from the exchange.

***

### Trust score and user levels

FaceVault assigns a **0–100 trust score** and a decision on every completion:

| Score   | Decision | KYC tab badge (typical) |
| ------- | -------- | ----------------------- |
| ≥ 70    | Accept   | Verified by FaceVault   |
| 40 – 69 | Review   | Under review            |
| < 40    | Reject   | Verification failed     |

The plugin updates the **in-tab status badge** automatically when users finish verification.

Promoting HollaEx **user verification levels** (for example, level 1 → 2) based on FaceVault results may require additional server-side wiring (for example, a webhook receiver that maps trust scores to kit user levels). See [FaceVault integrations](https://facevault.id/integrations/) for optional level-mapping guidance.

***

### Optional — FaceVault dashboard webhook

For server-side notifications (dashboard alerts, custom automation, or level mapping outside the webview), you can configure a webhook on your FaceVault **Site** under the **For developers** tab in [devdash.facevault.id](https://devdash.facevault.id):

1. Set your **Webhook URL** (HTTPS endpoint you control).
2. Copy the **signing secret** when shown (shown once).
3. Verify callbacks using the `X-FaceVault-Signature` HMAC-SHA256 header against the raw request body.

Webhook payloads include session ID, trust score, trust decision, and related verification fields. Details: [FaceVault API — Webhooks](https://facevault.id/docs).

{% hint style="info" %}
This marketplace plugin does not expose API keys or webhook secrets in HollaEx **public\_meta** — only the **slug** is required in the operator UI. API keys are used in the FaceVault developer dashboard for direct API / widget integrations, not for the standard hosted-page + HollaEx plugin flow.
{% endhint %}

***

### What gets verified

* **ID document** — capture, OCR (MRZ and national ID engines), and document fraud signals.
* **Liveness** — selfie flow with multi-signal anti-spoofing.
* **Face match** — dual-model face-match ensemble (ArcFace + AdaFace) comparison between ID portrait and selfie.
* **Proof of address** — optional, depending on your site’s verification level (Standard vs Strict).
* **Trust scoring** — combined accept/review/reject decision.

***

### Benefits

The FaceVault plugin saves exchange operators time by automating most identity checks. Manual review is only needed for borderline **review** outcomes. Verification typically completes in under a minute instead of hours or days of staff queue time.

Because verification runs on FaceVault’s hosted page, you avoid building a custom capture UI in the kit. The plugin is compatible with **HollaEx Cloud** restrictions (no third-party plugin server scripts).

For pricing, comparisons with other providers, and OPEX / API integrations, visit [facevault.id/integrations](https://facevault.id/integrations/).


# Shufti Pro - KYC & AML Verification

## Available To

Free for the following exchange plans:

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **Shufti Pro Plugin** is a KYC and AML verification solution integrated into HollaEx exchanges. Powered by [Shufti Pro](https://shuftipro.com), it provides a hosted, multi-language identity verification flow with support for ID, passport, driving licence, address checks, and selfie/video liveness.

The plugin generates a verification URL per user, redirects the user to Shufti Pro's hosted page, listens for status updates via a secure webhook (which is then re-verified against the Shufti Pro API to prevent spoofing), and updates the user's `id_data.status` status on the exchange. Admins can also manually verify or revoke a user's KYC status from the operator dashboard.

## Who Needs It?

This plugin is essential for any HollaEx exchange operator that:

* Needs to meet regulatory KYC/AML requirements.
* Wants to automate and streamline customer onboarding.
* Operates in regions where compliance is mandatory for trading, deposits, or withdrawals.
* Aims to reduce fraud while building trust and security with its user base.

## How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, input your **Shufti Pro Client ID** and **Secret Key**.

#### 1. Get your Shufti Pro credentials

1. Log in to the [Shufti Pro Backoffice](https://backoffice.shuftipro.com).
2. Open **Profile → API Keys** (or **Developer Tools**) and copy your **Client ID** and **Secret Key**. They are used together as HTTP Basic auth on every API call (Client ID = username, Secret Key = password), and the Secret Key also signs incoming webhooks.

#### 2. Set up the webhook in Shufti Pro

The plugin exposes a webhook endpoint that Shufti Pro will call when a verification completes. The callback URL is set automatically per inquiry, but if you need to whitelist or pre-configure it, it is:

```
https://<your exchange url>/api/plugins/shufti/webhook
```

The plugin re-verifies every webhook against the Shufti Pro API before changing a user's status, so payloads cannot be spoofed.

#### 3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                          | Required | Default                            | Description                                                                                                         |
| ------------------------------ | -------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `url` (public)                 | yes      | `https://api.shuftipro.com/`       | Shufti Pro API base URL.                                                                                            |
| `language` (public)            | no       | `EN`                               | Default language for the hosted page (ISO 639-1, e.g. `EN`, `FR`, `ES`).                                            |
| `verification_mode` (public)   | no       | `any`                              | Hosted-page verification mode. Allowed: `any`, `image_only`, `video_only`.                                          |
| `supported_documents` (public) | no       | `id_card,passport,driving_license` | Comma-separated list of accepted document types (`id_card`, `passport`, `driving_license`, `credit_or_debit_card`). |
| `client_id`                    | **yes**  | —                                  | Shufti Pro Client ID (Basic Auth username).                                                                         |
| `secret_key`                   | **yes**  | —                                  | Shufti Pro Secret Key (Basic Auth password and webhook signature key).                                              |

Once configured, all verification requests and responses will be automatically managed between HollaEx and Shufti Pro.

***

## **Benefits for HollaEx Operators**

The Shufti Pro plugin lets exchange operators offer a polished, multi-language KYC experience without building any verification UI in-house. It supports a wide catalogue of document types across 200+ countries, gives you a choice between fast image-only verification and stricter video liveness, and keeps the user's on-chain status in lock-step with Shufti's authoritative result. The result is faster customer onboarding, lower fraud, and regulatory compliance with a single integration.


# Scorechain - Crypto Risk Screening

Free for the following exchange plans:

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **Scorechain Plugin** is a crypto AML and risk-scoring solution integrated into HollaEx exchanges. Powered by [Scorechain](https://www.scorechain.com), it automatically reviews on-hold deposits and withdrawals against Scorechain's blockchain analytics and decides whether they are safe to release or should be kept for manual review.

For deposits, the plugin queries the on-chain transaction ID against Scorechain's `/scoringAnalysis` endpoint. For withdrawals, the destination address is queried instead. Scorechain returns a risk severity (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `NO_RISK`) plus the entity classification (exchange, mixer, sanctioned entity, scam, etc.). Transactions whose severity meets or exceeds your configured threshold are kept on hold, and the audit team is alerted by email. Everything else is auto-released.

## Who Needs It?

This plugin is essential for any HollaEx exchange operator that:

* Needs to comply with FATF, sanctions, and AML/CFT regulations.
* Wants automatic blocking of deposits and withdrawals tied to mixers, sanctioned entities, dark markets, scams, or stolen funds.
* Prefers a discrete severity-based risk model (CRITICAL / HIGH / MEDIUM / LOW).
* Wants to reduce the manual workload of compliance reviewers by auto-releasing low-risk traffic.

### How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, configure the plugin meta with your Scorechain API key and risk threshold.

#### 1. Get your Scorechain API key

1. Log in to the [Scorechain App](https://app.scorechain.com).
2. Open **Profile → API Keys**.
3. Generate a new API key and store it somewhere secure. It is sent on every request as the `X-API-KEY` header.

#### 2. Enable manual review of deposits and withdrawals

The plugin only acts on transactions that have been placed **on hold**. For pending deposits and withdrawals to land in the on-hold queue, both auto-processing flags must be turned **off** in your kit configuration:

* `kit.auto_deposit.active = false`
* `kit.auto_withdrawal.active = false`

If both are enabled, the plugin will log that there is nothing to process and exit each cycle.

> **The plugin does this for you automatically on first install.** When the plugin starts for the first time it will switch both `auto_deposit` and `auto_withdrawal` **off** in your kit configuration and send a one-time alert email to the audit recipient (or to the address configured in `alert_email`) explaining what changed and what it means for transactions. After that, the change is recorded in Redis and will not be reapplied — you remain free to re-enable either toggle at any time from **Operator Control → General → Security**, but doing so will silently disable Scorechain screening for that flow because there will be no on-hold queue for the plugin to read.

#### 3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                       | Required | Default                         | Description                                                                                                                                                  |
| --------------------------- | -------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `api_url`                   | no       | `https://api.scorechain.com/v1` | Scorechain REST API base URL.                                                                                                                                |
| `api_key`                   | **yes**  | —                               | Scorechain API key (sent as `X-API-KEY` header).                                                                                                             |
| `min_usdt`                  | no       | `100`                           | Minimum USDT-equivalent value before Scorechain is queried. Anything below this is auto-released without an external check.                                  |
| `risk_threshold`            | no       | `HIGH`                          | Severity at or above which a transaction is held. Allowed: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`.                                                              |
| `transaction_analysis_type` | no       | `ASSIGNED`                      | Scorechain `analysisType` for deposit transactions. `ASSIGNED` is the fastest (entity classification only). `INCOMING`, `OUTGOING`, `FULL` trace fund flows. |
| `address_analysis_type`     | no       | `ASSIGNED`                      | Scorechain `analysisType` for withdrawal destination addresses. Same options as above.                                                                       |
| `cache_ttl_hours`           | no       | `48`                            | How long to cache risk results per address/transaction id.                                                                                                   |
| `request_timeout_ms`        | no       | `8000`                          | Per-request timeout for Scorechain calls.                                                                                                                    |
| `alert_email`               | no       | (audit email from kit secrets)  | Optional override for the recipient of risk-hold alert emails.                                                                                               |

#### 4. (Optional) Trigger a manual run

The plugin runs automatically every 60 seconds. To trigger an immediate cycle, send an authenticated request from an admin account:

```
POST https://<your exchange url>/plugins/scorechain/run
Authorization: Bearer <admin token>
```

### What gets blocked

A pending deposit or withdrawal is kept **on hold** when its Scorechain severity meets or exceeds the configured `risk_threshold`. Otherwise, it is automatically released. When a transaction is held, an alert email is sent to the configured recipient (or the kit's audit email) with severity, score, and any matched entity name/type for follow-up.

### Supported networks

Maps HollaEx network identifiers to Scorechain `BlockchainsEnum` values for: Bitcoin, Ethereum, Tron, BSC, Polygon, Solana, Avalanche, Arbitrum, Base, Optimism, Ripple, Stellar, TON, Tezos, Litecoin, Bitcoin Cash, Dogecoin, Dash, Mantle, Blast, and Ink. Transactions on networks not in the map are skipped (left untouched) by the plugin.

***

#### **Benefits for HollaEx Operators**

The Scorechain plugin gives exchange operators a fast, severity-based risk gate on every deposit and withdrawal. It blocks transactions tied to mixers, sanctions, dark markets, and scams before settlement, while auto-releasing the bulk of clean traffic so compliance staff can focus on real cases. The result is stronger AML coverage, lower regulatory risk, and faster customer experience for legitimate users.


# AMLBot - Crypto Risk Screening

## Available To

Free for the following exchange plans:

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **AMLBot Plugin** is a crypto AML and KYT (Know Your Transaction) screening solution integrated into HollaEx exchanges. Powered by [AMLBot](https://amlbot.com), it automatically reviews on-hold deposits and withdrawals and decides whether they are safe to release or should be kept for manual review.

For each pending transaction, the plugin queries the AMLBot KYT API with the on-chain transaction hash (deposits) or destination address (withdrawals). AMLBot returns an overall risk score (0–100%), a granular breakdown of risk signals (sanctions, dark markets, scams, stolen coins, ransom, terrorism financing, mixers, etc.), and a blacklist flag.&#x20;

Transactions whose risk profile exceeds your configured thresholds stay on hold, and the audit team is alerted by email. Transactions are auto-released only after AMLBot returns a completed/successful result below the configured thresholds.

## Who Needs It?

This plugin is essential for any HollaEx exchange operator that:

* Needs to comply with AML, sanctions, and Travel Rule regulations.
* Wants to automatically catch deposits/withdrawals tied to scams, dark markets, sanctioned actors, or stolen funds before settlement.
* Prefers a percentage-based, tunable risk engine instead of a binary blocklist.
* Wants to reduce the operational load on compliance reviewers by auto-releasing low-risk traffic.

## How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, configure the plugin meta with your AMLBot credentials and risk thresholds.

#### 1. Get your AMLBot API credentials

1. Log in to [web.amlbot.com](https://web.amlbot.com).
2. Open your **Profile → API Keys**.
3. Generate an `accessId` and `accessKey` pair and store them somewhere secure. They are used together to compute the MD5 token sent on every API call.

#### 2. Enable manual review of deposits and withdrawals

The plugin only acts on transactions that have been placed **on hold**. Disable the auto-processing flag for each flow you want AMLBot to screen:

* `kit.auto_deposit.active = false` for deposit screening.
* `kit.auto_withdrawal.active = false` for withdrawal screening.

If both are enabled, the plugin will log that there is nothing to process and exit each cycle.

> **The plugin does this for you automatically on first install.** When the plugin starts for the first time it will switch both `auto_deposit` and `auto_withdrawal` **off** in your kit configuration and send a one-time alert email to the audit recipient (or to the address configured in `alert_email`) explaining what changed and what it means for transactions. After that, the change is recorded in Redis and will not be reapplied — you remain free to re-enable either toggle at any time from **Operator Control → General → Security**, but doing so will silently disable AMLBot screening for that flow because there will be no on-hold queue for the plugin to read.

#### 3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                                | Required | Default                        | Description                                                                                                                                                                                                                                                          |
| ------------------------------------ | -------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_url`                            | no       | `https://extramlbot.com`       | AMLBot KYT check endpoint URL. The plugin posts directly to this URL and falls back to `/check/` only if the endpoint is unavailable.                                                                                                                                |
| `access_id`                          | **yes**  | —                              | AMLBot `accessId`.                                                                                                                                                                                                                                                   |
| `access_key`                         | **yes**  | —                              | AMLBot `accessKey`. Used together with `access_id` to compute the MD5 token for each request.                                                                                                                                                                        |
| `min_usdt`                           | no       | `100`                          | Minimum USDT-equivalent value before AMLBot is queried. Anything below this is auto-released without an external check.                                                                                                                                              |
| `risk_threshold_percent`             | no       | `50`                           | Overall risk score percentage (0–100) at or above which a transaction is held.                                                                                                                                                                                       |
| `high_risk_signal_threshold_percent` | no       | `1.5`                          | Combined percent of high-risk signal categories (sanctions, dark\_market, scam, stolen\_coins, terrorism\_financing, etc.) at or above which a transaction is held, regardless of overall score. AMLBot signal fractions are converted to percent before comparison. |
| `risk_threshold`                     | no       | `HIGH`                         | Discrete severity bucket at or above which a transaction is held. Allowed: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`.                                                                                                                                                      |
| `block_on_blacklist`                 | no       | `true`                         | Hold any transaction with an AMLBot blacklist flag, regardless of risk percentage.                                                                                                                                                                                   |
| `check_withdrawals`                  | no       | `true`                         | Set to `false` for deposits-only mode.                                                                                                                                                                                                                               |
| `flow`                               | no       | `fast`                         | AMLBot analysis flow. `fast` returns quicker; `advanced` returns the full report.                                                                                                                                                                                    |
| `cache_ttl_hours`                    | no       | `48`                           | How long to cache risk results per address/transaction.                                                                                                                                                                                                              |
| `request_timeout_ms`                 | no       | `15000`                        | Per-request timeout for AMLBot calls.                                                                                                                                                                                                                                |
| `alert_email`                        | no       | (audit email from kit secrets) | Optional override for the recipient of risk-hold alert emails.                                                                                                                                                                                                       |

#### 4. (Optional) Trigger a manual run

The plugin runs automatically every 60 seconds. To trigger an immediate cycle, send an authenticated request from an admin account:

```
POST https://<your exchange url>/plugins/amlbot/run
Authorization: Bearer <admin token>
```

#### What gets blocked

A pending deposit or withdrawal is kept **on hold** when **any** of the following is true:

* `block_on_blacklist` is on AND the address has an AMLBot blacklist flag, OR
* The overall risk score percentage is greater than or equal to `risk_threshold_percent`, OR
* The sum of high-risk signal percentages is greater than or equal to `high_risk_signal_threshold_percent`, OR
* The discrete severity bucket meets or exceeds `risk_threshold`.

Pending, failed, or otherwise incomplete AMLBot API statuses are kept on hold and retried in a later cycle; they do not trigger a risk alert because the risk calculation is not ready. Otherwise, the transaction is automatically released. When a transaction is held due to risk, an alert email is sent to the configured recipient (or the kit's audit email) with the severity, percentages, blacklist flag, and the AMLBot UID for follow-up.

***

## **Benefits for HollaEx Operators**

The AMLBot plugin gives exchange operators continuous, automated AML coverage without growing the compliance team. It blocks high-risk deposits and withdrawals before settlement, auto-releases the long tail of clean traffic, and gives you fine-grained control over how strict the engine should be — by overall score, by signal category, or by simple severity buckets. The result is stronger AML posture, fewer manual reviews, and faster customer experience for legitimate users.


# TRM Labs - Wallet & Transaction Risk Screening

### Available To

Free for the following exchange plans:

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

### What Is It?

The **TRM Labs Plugin** is a blockchain risk and AML screening solution integrated into HollaEx exchanges. Powered by [TRM Labs](https://www.trmlabs.com), it automatically reviews on-hold deposits and withdrawals against the **TRM Wallet Screening API** to surface sanctions exposure, illicit-activity ties, and counterparty risk before funds move on or off the exchange.

For each pending transaction, the plugin queries TRM Labs' risk intelligence — covering 190+ blockchains and over 1.9B+ digital assets — and returns a numerical risk score (1–15), risk indicators (sanctions, scams, dark markets, ransom, stolen coins, etc.), associated entities, and direct links to the TRM Labs investigation app. Low-risk transactions are auto-released; high-risk ones stay on hold, and a configurable audit recipient is alerted by email.

If the API key is provisioned only for the free **TRM Sanctions API**, the plugin transparently falls back to that endpoint, so you still get sanctions screening at a minimum.

### Who Needs It?

This plugin is essential for any HollaEx exchange operator that:

* Needs to comply with FATF Travel Rule, OFAC, and other sanctions regimes.
* Wants to automatically block deposits/withdrawals from sanctioned, scam, ransomware, dark-market, or stolen-funds addresses.
* Operates in regions where transaction monitoring and pre-screening are mandatory.
* Wants to reduce the manual workload of compliance reviewers by auto-releasing low-risk pending transactions.

### How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, configure the plugin meta with your TRM Labs credentials and risk thresholds.

#### 1. Get your TRM Labs API key

1. Log in to your [TRM Labs dashboard](https://www.trmlabs.com).
2. Open your profile in the upper right and click **Configure Environment**.
3. Click **API Tokens** in the side navigation.
4. Click **Create new Client API token**, copy the API key, and store it somewhere secure.

#### 2. Enable manual review of deposits and withdrawals

The plugin only acts on transactions that have been placed **on hold**. For pending deposits and withdrawals to land in the on-hold queue, both auto-processing flags must be turned **off** in your kit configuration:

* `kit.auto_deposit.active = false`
* `kit.auto_withdrawal.active = false`

If both are enabled, the plugin will log that there is nothing to process and exit each cycle.

> **The plugin does this for you automatically on first install.** When the plugin starts for the first time it will switch both `auto_deposit` and `auto_withdrawal` **off** in your kit configuration and send a one-time alert email to the audit recipient (or to the address configured in `alert_email`) explaining what changed and what it means for transactions. After that, the change is recorded in Redis and will not be reapplied — you remain free to re-enable either toggle at any time from **Operator Control → General → Security**, but doing so will silently disable TRM Labs screening for that flow because there will be no on-hold queue for the plugin to read.

#### 3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                           | Required | Default                        | Description                                                                                                                                        |
| ------------------------------- | -------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_url`                       | no       | `https://api.trmlabs.com`      | TRM Labs API base URL.                                                                                                                             |
| `api_key`                       | **yes**  | —                              | The Client API token created above. Sent as HTTP Basic auth (key as both username and password).                                                   |
| `min_usdt`                      | no       | `100`                          | Minimum USDT-equivalent value before TRM Labs is queried. Anything below this is auto-released without an external check.                          |
| `min_risk_score_level`          | no       | `10`                           | TRM uses a 1–15 risk score level (1–4 Low, 5–9 Medium, 10–14 High, 15 Severe). Held when the highest indicator level meets or exceeds this number. |
| `risk_volume_percent_threshold` | no       | `25`                           | Percentage of the wallet's volume that came from risky counterparties at or above which a transaction is held.                                     |
| `risk_threshold`                | no       | `HIGH`                         | Discrete severity bucket at or above which a transaction is held. Allowed: `SEVERE`/`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`.                           |
| `block_on_sanctions`            | no       | `true`                         | When true, any address with a sanctions hit is held regardless of risk score.                                                                      |
| `check_withdrawals`             | no       | `true`                         | Set to `false` for deposits-only mode.                                                                                                             |
| `cache_ttl_hours`               | no       | `48`                           | How long to cache risk results per address.                                                                                                        |
| `request_timeout_ms`            | no       | `10000`                        | Per-request timeout for TRM Labs calls.                                                                                                            |
| `alert_email`                   | no       | (audit email from kit secrets) | Optional override for the recipient of risk-hold alert emails.                                                                                     |

#### 4. (Optional) Trigger a manual run

The plugin runs automatically every 60 seconds. To trigger an immediate cycle, send an authenticated request from an admin account:

```
POST https://<your exchange url>/plugins/trmlabs/run
Authorization: Bearer <admin token>
```

#### What gets blocked

A pending deposit or withdrawal is kept **on hold** when **any** of the following is true:

* `block_on_sanctions` is on AND TRM flags a sanctions risk indicator or a sanctioned-entity hit, OR
* The highest TRM risk score level is greater than or equal to `min_risk_score_level`, OR
* The address risk volume percent is greater than or equal to `risk_volume_percent_threshold`, OR
* The discrete severity bucket meets or exceeds `risk_threshold`.

Otherwise, the transaction is automatically released. When a transaction is held, an alert email is sent to the configured recipient (or the kit's audit email) with the severity, score, category, sanctions flag, top entity, risk volume %, total volume, and a direct link to the TRM Labs investigation app.

#### Supported networks

The plugin maps HollaEx network identifiers to TRM Labs chain identifiers for the most common networks: Bitcoin, Ethereum (and Classic), Tron, BSC, Polygon, Solana, Avalanche, Arbitrum, Base, Optimism, Ripple, Stellar, TON, Tezos, Litecoin, Bitcoin Cash, Dogecoin, Dash, Cardano, Polkadot, Near, Algorand, Aptos, Sui, Celo, Fantom, Hedera, Klaytn, Linea, Mantle, ZKsync, Gnosis, Sei, and Zcash. Transactions on networks not in the map are skipped (left untouched) by the plugin.

***

**Benefits for HollaEx Operators**

The TRM Labs plugin gives exchange operators institutional-grade blockchain intelligence without building it in-house. It blocks sanctioned, scam, ransomware, and dark-market addresses before funds settle, auto-releases the long tail of low-risk traffic so compliance staff aren't drowning in queues, and keeps every decision auditable through the TRM Labs investigation links delivered with each alert. The result is a stronger AML posture, lower regulatory exposure, and faster customer experience for clean transactions.


# Telynx - SMS Notifications & Verifications

## Available To

Free for the following exchange plans:

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **Telnyx Plugin** is an SMS gateway integrated into HollaEx exchanges. Powered by [Telnyx](https://telnyx.com), it sends transactional SMS messages from your exchange — deposit/withdrawal notifications and verification codes for actions like withdrawals, password resets, and 2FA — using your Telnyx account and your own sender phone number.

The plugin auto-formats numbers, picks the right routing for each destination country, and integrates directly with the exchange's user verification flow so users who opt in to SMS receive the same codes they would otherwise receive via email or authenticator app.

## Who Needs It?

This plugin is suitable for any HollaEx exchange operator that:

* Wants to send SMS notifications for deposits and withdrawals.
* Wants to offer SMS as a second factor for sensitive actions (withdrawals, password reset).
* Already has a Telnyx account, or prefers Telnyx's pricing/coverage over alternative SMS providers.

## How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, input your Telnyx API key and sender number.

#### 1. Get your Telnyx credentials

1. Log in to the [Telnyx Mission Control Portal](https://portal.telnyx.com).
2. Go to **Auth → API Keys** and create a new V2 API key. Copy it somewhere secure.
3. Go to **Numbers → My Numbers** and confirm the sender number you want to use. The number must be in **E.164 format** (e.g. `+15555550123`).
4. Make sure the number has the **Messaging** profile enabled.

#### 2. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                        | Required | Default | Description                                                                                               |
| ---------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `apiKey`                     | **yes**  | —       | Telnyx V2 API key.                                                                                        |
| `fromNumber`                 | **yes**  | —       | Telnyx sender phone number in E.164 format (e.g. `+15555550123`).                                         |
| `send_sms_deposit`           | no       | `true`  | Send deposit SMS notifications to users.                                                                  |
| `send_sms_withdrawal`        | no       | `true`  | Send withdrawal SMS notifications to users.                                                               |
| `send_sms_user_verification` | no       | `true`  | Send user-action verification codes (e.g. withdrawal, password reset) via SMS when the user has opted in. |

Once configured, the exchange will automatically route SMS through Telnyx for any user who has a verified phone number and has opted in to SMS notifications.

***

## **Benefits for HollaEx Operators**

The Telnyx plugin gives exchange operators a reliable, low-cost SMS channel for both transactional alerts and security-critical verification codes. Telnyx's global coverage and direct carrier connections deliver predictable latency and high throughput, so users get their codes in seconds even during peak load. Combined with the exchange's existing email and authenticator-app channels, SMS rounds out a multi-factor security posture that meets users wherever they are.


# Sinch - SMS Notifications & Verifications

## Available To

Free for the following exchange plans:

* **Cloud Plans:**
  * Enterprise
* **On-Premise Plans:**
  * Enterprise Unlimited

## What Is It?

The **Sinch Plugin** is an SMS gateway integrated into HollaEx exchanges. Powered by [Sinch](https://www.sinch.com), it sends transactional SMS messages from your exchange — deposit/withdrawal notifications and verification codes for actions like withdrawals, password resets, and 2FA — using your Sinch SMS service plan and your own sender phone number or short code.

The plugin auto-formats numbers, routes traffic through your selected Sinch region (US, EU, AU, BR, CA), and integrates directly with the exchange's user verification flow so users who opt in to SMS receive the same codes they would otherwise receive via email or authenticator app.

## Who Needs It?

This plugin is suitable for any HollaEx exchange operator that:

* Wants to send SMS notifications for deposits and withdrawals.
* Wants to offer SMS as a second factor for sensitive actions (withdrawals, password reset).
* Already has a Sinch account, or prefers Sinch's regional coverage and short-code support over alternative SMS providers.

## How to Use It?

You can simply install the plugin from the **Plugins** section inside the Operator Control. After installation, input your Sinch service plan ID, API token, and sender number.

#### 1. Get your Sinch credentials

1. Log in to the [Sinch Customer Dashboard](https://dashboard.sinch.com).
2. Go to **SMS → APIs** and select (or create) a Service Plan. Copy the **Service Plan ID** and **API Token**.
3. Provision or whitelist a sender — either a long phone number in **E.164 format** (e.g. `+15555550123`) or a short code/alphanumeric sender ID supported in your destination countries.
4. Note the **region** your service plan is provisioned in (`us`, `eu`, `au`, `br`, `ca`).

#### 2. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                        | Required | Default | Description                                                                                               |
| ---------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `servicePlanId`              | **yes**  | —       | Sinch SMS service plan ID.                                                                                |
| `apiToken`                   | **yes**  | —       | Sinch SMS API token.                                                                                      |
| `fromNumber`                 | **yes**  | —       | Sinch sender phone number or short code.                                                                  |
| `region`                     | no       | `us`    | Sinch SMS API region. Allowed: `us`, `eu`, `au`, `br`, `ca`.                                              |
| `send_sms_deposit`           | no       | `true`  | Send deposit SMS notifications to users.                                                                  |
| `send_sms_withdrawal`        | no       | `true`  | Send withdrawal SMS notifications to users.                                                               |
| `send_sms_user_verification` | no       | `true`  | Send user-action verification codes (e.g. withdrawal, password reset) via SMS when the user has opted in. |

Once configured, the exchange will automatically route SMS through Sinch for any user who has a verified phone number and has opted in to SMS notifications.

***

## **Benefits for HollaEx Operators**

The Sinch plugin gives exchange operators a globally distributed SMS channel with strong regional presence in the US, Europe, APAC, and Latin America. Region-pinned routing keeps latency low and improves carrier-level deliverability for jurisdictions that are hard to reach with generic providers. Combined with the exchange's existing email and authenticator-app channels, SMS rounds out a multi-factor security posture that meets users wherever they are.


# AWS SNS - SMS Messaging

AWS SNS offers another way to notify users of activity on their accounts, sending quick texts for withdrawals and deposits.

## What Is It?

The AWS SNS (Amazon Web Services Simple Notification Service) plugin sends a text message (assuming the user has supplied their phone number) notifying them of deposits and withdrawals.

<figure><img src="/files/3leM7qfVKutboZxbOcgm" alt=""><figcaption></figcaption></figure>

## Who Needs It?

Operators who want to give another method of communicating with their exchange's users, making users feel at ease that the system will alert them directly to their mobile in the case of any behavior.

## How to Use It?

{% hint style="info" %}
Please refer to the[ ](https://docs.hollaex.com/plugins/use-plugins/kyc#aws-s3-bucket-setup)[AWS IAM key creation](#aws-resources) section first, before proceeding.
{% endhint %}

After installing AWS SNS on the exchange, we will be given several fields to fill in. Filling these out requires [AWS IAM credentials](#access-key-generation) with AWS SNS access. You'll need to create an IAM policy for the IAM user that you intend to use. This policy will ensure that the user has the appropriate permissions to access and use the SMS service.

The region should follow the[ region code](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions) of AWS.

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

With this done, the option of whether texts should be sent for both withdrawals and deposits is made. Save that and be ready to go- just ensure to monitor the AWS IAM settings from time to time, to ensure that you have credits for sending messages.

***

For a user, this will look like the following for withdrawals and deposits:&#x20;

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

## AWS Resources <a href="#aws-resources" id="aws-resources"></a>

This is required to create a new IAM user to connect to the HollaEx AWS SNS plugin. The plugin will interact with AWS SNS to send text messages.

Please follow the guide below to create the resources.

### Automated resource creation <a href="#automated-resource-creation" id="automated-resource-creation"></a>

{% hint style="info" %}
You can apply a Cloudformation template to automate the IAM creation with a simple click and a few types. Please click the link below to proceed. The AWS Web console will pop up through the link. Please make sure to log in with your existing AWS account.&#x20;

[🔗 Cloudformation Template](https://console.aws.amazon.com/cloudformation/home?region=eu-west-3#/stacks/quickcreate?templateURL=https://hollaex-plugins-cloudformaiton.s3.ap-southeast-1.amazonaws.com/cloudformation-sns.yaml\&stackName=hollaex-sms-sns)
{% endhint %}

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

Once you click the link, you'll see a page to set multiple parameters. Here are a few important ones:

* **Stack name**: The name of the Cloudformation stack. It is okay to leave it as default in most cases.
* **MyIAMUsername:** The name of the new AWS IAM username that you are going to create. This will be used as a name for the IAM user with AWS S3 access. For example: `sms-iam-user`

It is not needed to set the "Permissions - optional" panel in most cases. You can simply skip.

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

It will take a few minutes for the system to create the resources.

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

Once it's all completed, you'll be able to check the output. You'll get everything necessary, including the IAM Access Key and the Secret Key. Please save this information somewhere safe, and refer to the [docs](#how-to-use-it) to configure the plugin. You are all good now!

### Manual resource creation

You will need to make an IAM policy for an IAM user you are going to use for the SNS. You should create a new policy on the *AWS IAM Console - Access Management - Policies* page.

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "*"
        }
    ]
}
```

Now, go to the *AWS IAM Console - Access management - Users page* to create a new user for KYC connection. The above policy can then be bound to the account.

Get the access key ID and secret access key once the account is created. Once this account is created, in the user field, you will be provided the Access Key ID and Secret Access Key required.

![](/files/-MToRvZKeLLB57yMMrnu)


# Messente - SMS Service Integration

Messente is a way to enhance your exchange's offerings by allowing SMS messages to be sent to users for various notifications

## What is it?

Free to all operators, the Messente plugin enhances the user experience on your platform by providing customizable automatic SMS text messaging. This feature can be set up to execute at crucial moments, improving communication with your users. With this plugin, you can reach customers around the world using an easy-to-integrate messaging API.

## Who Needs It?

Operators who want to add to ann extra avenue of communication with their userbase.

## How to Get it

To get started, simply click the green ‘*install*’ button to install and enable the plugin on your exchange.

After installing the Messente plugin, you will need to configure the following items in the Private section.

* `password`: Messente API password
* `username`: Messente API username
* `sendername`: Optional. If set, you will need to configure the sender on Messente. You can leave it empty for simplicity.
* `send_sms_deposit`: Enable SMS notifications to users for deposits by selecting 'true'.
* `send_sms_withdrawal`: Enable SMS notifications to users for withdrawals by selecting 'true'.

Additionally, the ‘manually upgrade’ feature allows you to update the plugin to a newer version by uploading a .json file. This will preserve the current plugin's configuration values, ensuring that your settings are not lost during the upgrade process.


# CoinMarketCap - Market Data Integration

CoinMarketCap is one of the leading names in crypto. With this plugin you can make it easier to get listed

## Available To

Available to all operators for a one-time fee of $500

## What Is It?

The CoinMarketCap (CMC) plugin is a simple behind-the-scenes plugin, that aligns your HollaEx exchanges with CMC's standards for exchange listing.&#x20;

It does this by creating a set of API endpoints that are integrated into your exchange's platform, allowing for seamless communication with CoinMarketCap.

CoinMarketCap's requirements for endpoints are listed [here](https://tinyurl.com/y2hj58pd). Below you can find the plugin endpoints:

* `GET <yourexchange_api_url>/plugins/cmc/summary`
* `GET <yourexchange_api_url>/plugins/cmc/assets`
* `GET <yourexchange_api_url>/plugins/cmc/tickers`
* `GET <yourexchange_api_url>/plugins/cmc/orderbook`
* `GET <yourexchange_api_url>/plugins/cmc/trades`

With the CoinMarketCap Plugin, the CMC technical team no longer has to go through the tedious process of manually converting your HollaEx-based exchange API, which makes it easy for CoinMarketCap to integrate with your exchange from a technical perspective.

In addition to making the exchange listing process easier, the CoinMarketCap Plugin also provides real-time updates and insights into your exchange's performance on CoinMarketCap. This allows you to track your exchange's ranking, trading volume, and other key metrics in real-time, enabling you to make data-driven decisions to optimize your exchange's performance.

## Who Needs It?

The CoinMarketCap Plugin is a must-have tool for any operator looking to streamline the exchange listing process and also gain valuable insights into their performance on one of the world's leading cryptocurrency marketplaces.

## How to Use It?

Fortunately, this plugin requires little action on the exchange; simply pay for the invoice, and it can be installed; however, you will need to contact CMC to get listed.


# Cobo - Wallet Custody Integration (WaaS 2.0)

## Available To

Free for the following exchange plans:

* **On-Premise Plans:**
  * Enterprise Unlimited

{% hint style="info" %}
This is a **wallet custody** plugin and is only available on **On-Premise** **(Unlimited)** plans. It is not offered on Cloud plans because custody must be controlled by the exchange operator's own infrastructure and Cobo API credentials.
{% endhint %}

## What Is It?

The **Cobo Plugin** connects a HollaEx exchange to [Cobo Wallet-as-a-Service 2.0](https://www.cobo.com/developers/v2) using a single shared **Custodial Asset Wallet** as the custody backend.

It exposes server-side routes only:

* Generates per-user deposit addresses inside the shared Cobo wallet.
* Credits are deposited when Cobo emits a `transactions.*` webhook, **and** the canonical state is reconfirmed via a signed `GET /v2/transactions/{id}` API call.
* Dispatches pending exchange withdrawals to Cobo every minute via `POST /v2/transactions/transfer`.
* Approves only those Cobo callback requests whose `request_id` matches a pending withdrawal it previously dispatched (auto-deny otherwise).

## Who Needs It?

This plugin is suitable for exchange operators that:

* Use Cobo as their custody provider.
* Want a single shared Custodial Asset Wallet with per-user deposit addresses across many chains.
* Want pending exchange withdrawals submitted to Cobo automatically.
* Need defense-in-depth on deposit credits: every webhook is re-verified against Cobo's authenticated API before any mint is created or updated.

## How to Use It?

Install the plugin from the **Plugins** section inside the Operator Control, then configure the plugin meta with your Cobo API credentials and shared wallet ID.

#### 1. Generate a Cobo API key pair

Cobo authenticates every API request with an Ed25519 key pair.

1. Generate a key pair using either the [Cobo CLI](https://www.cobo.com/developers/v2/guides/overview/cobo-auth#generate-an-api-key-and-an-api-secret), OpenSSL, or any Ed25519 library. The output you need is two 32-byte hex strings:
   * **API key** - the public key in hex (64 hex characters).
   * **API secret** - the private key in hex (64 hex characters).
2. Register the **public** key on Cobo Portal under **API Management -> Register an API key**, scoped at minimum to:
   * `wallet.read`, `wallet.create_address`
   * `transaction.read`, `transaction.withdraw`
   * `webhook.read`, `webhook.edit`, `callback.read`

#### 2. Create a shared Custodial Asset Wallet

1. In Cobo Portal, create a new wallet:
   * `wallet_type`: `Custodial`
   * `wallet_subtype`: `Asset`
2. Copy the wallet ID. This is the single shared wallet that will hold per-user deposit addresses for every supported chain. The plugin does **not** create one wallet per exchange user.

#### 3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                | Required | Default                       | Description                                                                                                                                                                                                                                     |
| -------------------- | -------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_url`            | **yes**  | `https://api.dev.cobo.com/v2` | Cobo WaaS 2.0 base URL. Use `https://api.cobo.com/v2` for production.                                                                                                                                                                           |
| `api_key`            | **yes**  | empty                         | Your Cobo API public key (hex, 64 chars).                                                                                                                                                                                                       |
| `api_secret`         | **yes**  | empty                         | Your Cobo API private key (hex, 64 chars). Used to sign every outbound API request.                                                                                                                                                             |
| `wallet_id`          | **yes**  | empty                         | ID of the shared Custodial Asset Wallet.                                                                                                                                                                                                        |
| `webhook_public_key` | **yes**  | dev key                       | Cobo's Ed25519 public key (hex) used to verify inbound webhooks and callbacks. Production: `8d4a482641adb2a34b726f05827dba9a9653e5857469b8749052bf4458a86729`. Development: `a04ea1d5fa8da71f1dcfccf972b9c4eba0a2d8aba1f6da26f49977b08a0d2718`. |

The plugin will not initialize unless all required values are present.

#### 4. Register the webhook endpoint on Cobo Portal

In Cobo Portal -> **Developers -> Webhook Endpoints**, register:

```
POST https://<your exchange url>/plugins/cobo/webhook
```

Subscribe, at minimum, to:

* `transactions.created`
* `transactions.updated`

Cobo signs every event with `Biz-Resp-Signature` and `Biz-Timestamp` request headers. The plugin verifies the signature and then **re-fetches the canonical transaction** via `GET /v2/transactions/{id}` so the on-disk state, not the webhook body, is what drives the deposit credit or withdrawal status update. Forged or stale webhooks are dropped.

#### 5. Register the callback endpoint on Cobo Portal

Cobo asks an external endpoint to approve every initiated withdrawal before it is signed and broadcast. In Cobo Portal -> **Developers -> Callback Endpoints**, register:

```
POST https://<your exchange url>/plugins/cobo/callback
```

The plugin replies in plain text with `ok` or `deny`:

* `ok` only when the callback's `request_id` matches a pending exchange withdrawal that the plugin previously dispatched and is in `waiting=true, status=false, dismissed=false, rejected=false` state.
* `deny` for everything else, including missing/invalid signature, unknown `request_id`, or burns that are no longer in the dispatched state.

#### 6. User-facing address creation

Authenticated users request a deposit address with:

```
GET https://<your exchange url>/plugins/cobo/create-address?crypto=<currency>&network=<network>
Authorization: Bearer <user token>
```

The plugin:

1. Returns `400` if the user already has a wallet for this currency+network.
2. Reuses any existing same-network address the user already has, if one exists.
3. Otherwise calls `POST /v2/wallets/{wallet_id}/addresses` with `{ chain_id, count: 1 }` and registers the returned address with `toolsLib.wallet.createUserWalletByKitId`.

#### 7. Withdrawal dispatch

Two cron jobs run every minute:

* `markPendingWithdrawalsProcessing` flips eligible pending burns to `processing: true`. A burn is eligible when it is **not** completed, dismissed, rejected, waiting, processing, or on hold, **and** its currency maps to a Cobo chain.
* `dispatchPendingWithdrawals` picks up `processing` burns, marks them `waiting: true, processing: false`, then calls `POST /v2/transactions/transfer` with:
  * `request_id` set to the exchange's burn `transaction_id` (used for callback matching and webhook reconciliation).
  * `source: { source_type: "Asset", wallet_id }`.
  * `token_id` resolved via `GET /v2/wallets/{wallet_id}/tokens?chain_ids=...` (cached in memory).
  * `destination.account_output: { address, amount }`.

Final completion is recorded later by the webhook (which re-verifies via the Cobo API) and writes `status: true` plus the on-chain hash to the exchange burn record.

To trigger dispatch manually as an admin:

```
POST https://<your exchange url>/plugins/cobo/dispatch-withdrawals
Authorization: Bearer <admin token>
```

#### Health check

```
GET https://<your exchange url>/plugins/cobo/health
```

### Supported networks

Supported chains are loaded from Cobo at startup via `GET /v2/wallets/chains?wallet_type=Custodial&wallet_subtype=Asset` and cached in memory. The plugin maps Cobo chain names to exchange currency symbols. Built-in mappings include Bitcoin, Bitcoin Cash, Litecoin, Dogecoin, Dash, Ethereum, Tron, Solana, Polygon, Avalanche C-Chain, Arbitrum, Optimism, Base, BNB Smart Chain, XRP, Cosmos, Algorand, Cardano, Polkadot, Stellar, Tezos, TON, NEAR, Aptos, and Sui. Other chains returned by Cobo are best-effort matched by name.

Assets without a mapped Cobo chain are skipped by the withdrawal dispatcher and cannot derive deposit addresses until the mapping is added.

***

#### **Defense-in-Depth**

Every completed credit deposit, and withdrawal, goes through two checks before the kit ledger is touched:

1. The inbound webhook signature is verified with Cobo's published Ed25519 public key.
2. The plugin then makes its own signed `GET /v2/transactions/{id}` call and uses **that** response (status, amount, destination address, transaction hash, request\_id) to decide what to credit or update.

Even if the webhook signature were ever bypassed or the public key were misconfigured, no funds move unless Cobo's authenticated API confirms the same transaction.

***

## **Benefits for HollaEx Operators**

The Cobo plugin lets an exchange keep custody operations inside Cobo while preserving the normal HollaEx wallet flow for users. Deposit addresses are derived from a shared Cobo Custodial Asset Wallet; deposit credits depend on signed Cobo webhooks **and** Cobo API confirmation, outgoing withdrawals are queued and dispatched on a cron, and Cobo callbacks for those withdrawals are auto-approved only when they match a known pending exchange burn. This keeps custody integration centralized, auditable, and aligned with the exchange's existing pending transaction workflow.


# HexTrust - Wallet Custody Integration

## Available To

Free for the following exchange plans:

* **On-Premise Plans:**
  * Enterprise Unlimited

{% hint style="info" %}
This is a **wallet custody** plugin and is only available on **On-Premise** plans. It is not offered on Cloud plans because custody must be controlled by the exchange operator's own infrastructure and HexTrust API credentials.
{% endhint %}

## What Is It?

The **HexTrust Plugin** connects a HollaEx exchange to HexTrust wallet custody. It creates user deposit addresses through HexTrust vaults, processes signed HexTrust webhooks for deposits and withdrawals, and dispatches eligible pending crypto withdrawals to HexTrust for on-chain settlement.

The plugin exposes server-side routes only. Users request deposit addresses through the exchange, while withdrawal dispatch runs automatically in the background and can also be triggered by an admin.

## Who Needs It?

This plugin is suitable for exchange operators that:

* Use HexTrust as their custody provider.
* Want user deposit addresses derived from HexTrust vaults.
* Want pending exchange withdrawals submitted to HexTrust automatically.
* Need signed webhook confirmation before deposits are credited or withdrawals are completed.

## How to Use It?

Install the plugin from the **Plugins** section inside the Operator Control, then configure the plugin meta with your HexTrust credentials and webhook verification key.

#### 1. Get your HexTrust credentials

Create or retrieve the following values from your HexTrust environment:

* API base URL for sandbox or production.
* Enterprise ID.
* API key.
* ES256 private key used to sign API requests.
* Ed25519 webhook public key used to verify HexTrust webhook payloads.
* Source withdrawal address used as the `from` address when submitting withdrawals.

Store the private key and API key securely. Do not commit live credentials into this repository.

#### 2. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

| Field                | Required | Default                                    | Description                                                                                                      |
| -------------------- | -------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `api_url`            | **yes**  | `https://api.sandbox.hexsafe.hextrust.com` | HexTrust API base URL. Use the correct sandbox or production URL for your account.                               |
| `enterprise_id`      | **yes**  | empty                                      | HexTrust enterprise ID.                                                                                          |
| `api_key`            | **yes**  | empty                                      | HexTrust API key.                                                                                                |
| `private_key`        | **yes**  | empty                                      | ES256 private key used to sign HexTrust API requests. Escaped `\n` newlines are accepted.                        |
| `webhook_public_key` | **yes**  | empty                                      | Base64 Ed25519 public key used to verify HexTrust webhooks. Webhooks are rejected if this is missing or invalid. |
| `withdrawal_address` | **yes**  | empty                                      | Address sent as `from` when the plugin submits withdrawals to HexTrust.                                          |

The plugin will not initialize unless all required values are present.

#### 3. Configure HexTrust webhooks

Configure HexTrust to send transaction webhooks to:

```
POST https://<your exchange url>/plugins/hextrust/webhook
```

Webhook requests must include a valid signature and payload in the format expected by HexTrust. The plugin verifies the signature before processing any deposit or withdrawal update.

#### 4. Address creation

Authenticated users can request a deposit address with:

```
GET https://<your exchange url>/plugins/hextrust/create-address?crypto=<currency>&network=<network>
Authorization: Bearer <user token>
```

The plugin creates or reuses the user's HexTrust vault, derives an address for the requested asset/network, and registers the address in the exchange wallet records. If a user already has a wallet on the same network, the plugin reuses that address for compatible assets.

#### 5. Withdrawal dispatch

The plugin checks pending exchange withdrawals every minute. Eligible withdrawals are submitted to HexTrust when they are:

* Not completed.
* Not dismissed.
* Not rejected.
* Not waiting.
* Not on hold.
* Marked for processing by the plugin.
* For a supported blockchain asset.

To trigger dispatch manually as an admin:

```
POST https://<your exchange url>/plugins/hextrust/dispatch-withdrawals
Authorization: Bearer <admin token>
```

After a withdrawal is submitted, it remains waiting for a HexTrust webhook. A completed withdrawal webhook updates the exchange burn as successful and stores the on-chain transaction hash when available.

### Supported Assets

The plugin loads supported chains from HexTrust at startup and maps HexTrust chain names to exchange symbols. Built-in mappings include Bitcoin, Ethereum, Optimism, Avalanche, Kaia, Bitcoin Cash, Celestia, Cosmos, XRP, Algorand, Stacks, Tezos, Injective, and several other networks returned by HexTrust.

Assets without a mapped HexTrust chain are skipped by the withdrawal dispatcher and may not be able to derive addresses until the mapping is added.

***

## **Benefits for HollaEx Operators**

The HexTrust plugin lets an exchange keep custody operations inside HexTrust while preserving the normal HollaEx wallet flow for users. Deposit addresses are derived from custody vaults, deposit credits depend on signed custody webhooks, and outgoing withdrawals are queued and dispatched without manual API calls. This keeps custody integration centralized, auditable, and aligned with the exchange's existing pending transaction workflow.


# Install Plugins

Configuring new plugins onto your exchange is incredibly simple and there are a few options possible.

## Installing via the Operator Control Panel

To install a plugin offered by HollaEx to access your  Operator Control Panel, head to the `Plugins` section and click on the `My plugins` tab.

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

In the 'Explore' section, you will see a list of the plugins offered by HollaEx.&#x20;

Adding Plugins from here could not be easier, simply hit the green 'Add' button on the right, or click on the plugin you are interested in for more details (also with a green 'Add' button with the same function).

<figure><img src="/files/7z0VABNYRmsifK3kYFsw" alt=""><figcaption><p>Plugin detail screen</p></figcaption></figure>

After clicking the 'Add' button, you will be automatically taken to the 'configuration' page for that plugin to set up any details that may be required.

## Adding Third Party Plugins via the Op. Controls

For plugins that are not on the Explore tab, the installation is fortunately still simple. From the Plugins page, head to the 'My Plugins' tab where the 'Add Third Party Plugin' button will be.

![](/files/-MhX88Vw8opLmIhkHwI_)

Clicking this button will give a confirmation screen asking for confirmation of adding the third-party plugin. At this point there are two options:

* Upload the plugin JSON file
* Input the plugin URL path

![](/files/-MhX8i5ki1KiUp4IF-0x)

Simply click the upload button and provide the JSON file of the desired plugin.

<figure><img src="/files/gdHtf52VgoZSJsUXFjDQ" alt=""><figcaption><p>The plugins folder has an exmaple plugin, with JSON</p></figcaption></figure>

If the uploaded file has the correct format, you will once again be asked to confirm the process by typing in 'I UNDERSTAND'.&#x20;

![](/files/-MhX8NCXHvn13gICpqxV)

Once confirmed, your plugin will be installed on your exchange, and you will be able to configure it.

<figure><img src="/files/xIsuWiIwSx8RzQ4TanBN" alt=""><figcaption><p>Taken to the config screen after uploading the JSON.</p></figcaption></figure>

## API

To install a plugin through the API, you can use the endpoint `POST <API_URL>/plugins` with the plugin JSON object above passed as the request body.&#x20;

## Install Plugin

<mark style="color:green;">`POST`</mark> `https://<API_URL>/plugins`

Install a plugin

#### Headers

| Name                                            | Type   | Description  |
| ----------------------------------------------- | ------ | ------------ |
| authorization<mark style="color:red;">\*</mark> | string | Bearer token |

#### Request Body

| Name                                      | Type    | Description                                           |
| ----------------------------------------- | ------- | ----------------------------------------------------- |
| public\_meta                              | object  | Plugin public\_meta object                            |
| type                                      | string  | Plugin type                                           |
| enabled                                   | boolean | Enable/disable the plugin on installation             |
| name<mark style="color:red;">\*</mark>    | string  | Name of plugin                                        |
| version<mark style="color:red;">\*</mark> | number  | Plugin version                                        |
| script                                    | string  | Plugin script                                         |
| meta                                      | object  | Plugin meta object                                    |
| prescript                                 | object  | Plugin prescript object. Valid keys: `install`, `run` |
| postscript                                | object  | Plugin postscript object. Valid keys: `run`           |
| icon                                      | string  | Plugin icon url                                       |
| documentation                             | string  | Plugin markdown documentation                         |
| url                                       | string  | Plugin url                                            |
| bio                                       | string  | Plugin simplified bio                                 |
| logo                                      | string  | Plugin logo url                                       |
| description                               | string  | Plugin long description                               |
| author<mark style="color:red;">\*</mark>  | string  | Plugin author                                         |
| admin\_view                               | string  | Plugin admin\_view                                    |
| web\_view                                 | string  | Plugin web\_view                                      |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "name": "hello-exchange",
    "version": 1,
    "author": "bitHolla",
    "enabled": true,
    "description": "Demo plugin for proof of concept",
    "bio": "Demo plugin",
    "public_meta": { ... },
    "web_view": null,
    "updated_at": "2021-03-08T04:06:03.357Z",
    "created_at": "2021-03-08T04:06:03.357Z",
    "documentation": null,
    "logo": null,
    "icon": null,
    "url": null,
    "enabled_admin_view": false

```

{% endtab %}
{% endtabs %}


# Developing Plugins

Developing a plugin for your exchange allows deeper customization of your exchange, without needing to worry too much about causing issues by altering the source code itself

![](/files/-MT-_lZ1UfmVui7sGD5k)

Taking your exchange to the next level requires customizations and features tailored to your needs.

Similar to WordPress or the App store, HollaEx provides an extensive framework for developing custom plugins, allowing you to create new features and provide customized services based on your requirements. As plugins are custom code, you can feasibly build anything you can imagine within the scope of the exchange.

In addition, since plugins are attached on top of the exchange, adding a plugin to your exchange will not interfere with your exchange's core and thus have less chance of problems from interfering with the source logic.&#x20;

Additionally, plugins can have an extensive user interface, both for users and the admin, so it will be more convenient to manage, rather than customizing the code base of HollaEx Kit directly.

## Docs Structure

These plugin pages are split into two sections. The first half, '[Development Walkthrough: Hello-Plugin](/plugins/develop-plugins/development-walkthrough-hello-plugin)' is the best place to start, to get an idea of the practical steps that go into the creation of a plugin. It only takes about 20 minutes from start to finish, so we recommend starting here.

After this, the '[Advanced](/plugins/develop-plugins/advanced)' section contains the specific details of each step that the walkthrough goes through, for those who want to learn more. In addition, two more advanced tutorials can be found to see examples of more complex plugins.


# Development Walkthrough: Hello-Plugin

For those who want to walk before they run, this (very) simple plugin will show the process of creating, configuring and running a plugin

## The Goal

Over the next few pages, each of the page's concepts will be illustrated with a simple example plugin *hello-plugin.*&#x20;

What the *hello-plugin* plugin will allow is calling the endpoint `GET /plugins/hello-plugin/info` and receive the following response:&#x20;

```javascript
{
    public_message: 'Hello Plugin!',
    private_message: 'Hello Plugin...',
    library_message: 'Hello Plugin NPM',
    moment_timestamp: <ISO_STRING>
    exchange_info: {...}
}
```

The elements of this output come from the following sources:

* `public_message`: A value set in `public_meta`.
* `private_message`: A value set in `meta`
* `library_message`: The string produced by our third-party npm library.
* `moment_timestamp`: Date ISO string produced by a default library (`moment`).
* `exchange_info`: The exchange's basic information.

We will also be adding a new page to our exchange `web_view` that displays our custom client-side interface for *hello-plugin*.

In the next sections, we go through all the components in the plugin and gradually develop each component for *hello-plugin* step by step.


# Initialization

First, we are going to run the command that will generate the plugin template we will be working on

{% hint style="info" %}
Initialization is a fairly simple step, but it does have a few options possible when defining the type of the plugin. Head over to the [*Advanced/Initialization*](/plugins/develop-plugins/advanced/initialization) section to see all the page types.
{% endhint %}

## First Step -  Dev Mode

To begin with, you are going to need to enable Dev mode. This is a vital step as it will allow 'on-the-fly-editing', giving the ability to see code changes quickly, without having to rebuild the exchange every time.

[Follow this page's steps,](/developers/run-dev-mode) and once you have the ability to see the 'hello-exchange' plugins information displayed on your browser (up to and including the '[*Checking Dev Mode is working*](/developers/run-dev-mode#checking-dev-mode-is-working)' section) come back here and let's get into making the new (very similar), '*hello-plugin*'.

## Initializing 'hello-plugin'

1. From the HollaEx root directory, in your terminal, access the plugins folder with: \
   `cd plugins`.
2. From the plugins folder, In order to initialize our *hello-exchange* plugin, run the following command:

<pre><code><strong>npm run add:plugin --plugin=hello-plugin --type=page
</strong></code></pre>

This command will create a *hello-plugin* folder in our plugins directory (`plugins/src/plugins/hello-plugin`), and will display a success message in the terminal (see below).

<figure><img src="/files/pI1b6MNDBiLOTvkK7okh" alt=""><figcaption><p>Accessing the plugins folder, and initializing the <em>hello-exchange</em> plugin</p></figcaption></figure>

Now the *hello-plugin* folder has been created for us (*hollaex-kit/plugins/src/plugins/hello-plugin*) with the command we just ran.&#x20;

Inside this *hello-plugin* folder will be a few other directories and files that will assist us, and where we will be adding our own files.

<figure><img src="/files/2bPI9MWASo6gZ3RabuFU" alt=""><figcaption><p>Our newly made hello-plugin, beside the hello-exchange that comes with the kit</p></figcaption></figure>


# Configuration

The config.json file we will create lays the basic foundation of out plugin, its name, author, description etc.

{% hint style="info" %}
Check the ['Advanced/ Config' page ](/plugins/develop-plugins/advanced/config)for a more in-depth look at what exactly the `config.json` file is, and a description of the attributes.
{% endhint %}

With the *hello-plugin* created within our HollaEx Kit folder, we can start building the`config.json` file with all the basic information needed, good idea here to get out your IDE of choice.&#x20;

1. Create a file named`config.json` inside the `server/` directory of the newly-created *hello-plugin* plugin.
2. Copy and paste the following code into this newly created file:

<details>

<summary>Click here for the <em>config.json</em> code for <em>hello-plugin</em></summary>

```json
{
    "name": "hello-plugin",
    "version": 1,
    "type": null,
    "author": "You",
    "bio": "A short description",
    "description": "This is a longer description of your first plugin",
	"documentation": null,
	"logo": null,
	"icon": null,
	"url": null,
    "public_meta": {
        "public_message": {
          "type": "string",
          "required": false,
          "description": "I can be seen by anyone!",
          "value": "Hello, this text is from the plugin!"
        }
      },
      "meta": {
        "private_message": {
          "type": "string",
          "required": false,
          "description": "I can only be seen by some!",
          "value": "Hi, this is the secret text from the plugin!"
		}
	},
	"prescript": {
		"install": ["hello-world-npm"],
		"run": null
	},
	"postscript": {
		"run": null
	}
}
```

</details>

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

And that's all for what we need to do on this page!&#x20;

**Continue onto the** [**next step**](/plugins/develop-plugins/development-walkthrough-hello-plugin/scripting), or read below for a bit more understanding of what this new *config.json* is actually doing.

## What Did We Just Do?

Looking at the interesting bits of the config.json we can see what we have actually defined.&#x20;

{% hint style="info" %}
Check the list [on this page](/plugins/develop-plugins/advanced/config), for full definitions of each element
{% endhint %}

```json
{
  "name": "hello-plugin",
  "version": 1,
  "type": null,
  "author": "You",
  "bio": "A short description",
  "description": "This is a longer description of your first plugin",
    ...
}
```

The top part of the code we see above is fairly simple. We have defined the name, version, and author of the plugin (hello-plugin, on version 1, written by 'You'), as well as created its bio - a quick description, and the description - a more detailed description seen on the plugins page.

{% hint style="info" %}
*Documentation*, *logo*, *icon*, and *url* are all set to null at the moment so we will ignore them for now.
{% endhint %}

```json
{
    ...
    "public_meta": {
      "public_message": {
        "type": "string",
        "required": false,
        "description": "I can be seen by anyone!",
        "value": "Hello, this text is from the plugin!"
      }
    },
    "meta": {
      "private_message": {
        "type": "string",
        "required": false,
        "description": "I can only be seen by some!",
        "value": "Hi, this is the secret text from the plugin!"
      }
    },
    ...
}
```

Moving to the next code chunk, we find `public-meta` and `meta`.

Again for a more in-depth view of what these are, check the [main config page](/plugins/develop-plugins/advanced/config). Both`public-meta` and `meta` are core aspects of plugins, so it's good to have a solid understanding of them.

For *hello-plugin,* we have one object within both  `public-meta` and `meta`,  `public-message` and `private_message` respectively, both being fairly similar.

* `type` : This defines the (surprisingly) type of the object, out of four allowed types (`number`, `string`, `boolean`, and `date-time`). In *hello-plugin*, both `public-meta` and `meta objects` will be strings
* `required` : Neither of these values are strictly required for *hello-plugin* to run
* `description` is the description of the values, here we are reminded that `public-meta` is not a secret and `meta` is a secret object.&#x20;
* `value` : Here we find the strings our plugin will actually use. We will see these values actually on the exchange in the appropriate places later.

```json
{
    ...
"prescript": {
    "install": ["hello-world-npm"],
    "run": null
  },
  "postscript": {
    "run": null
  }
    ...
}
```

At the end of our code, we have the `prescript` and `postscript`. In *hello-plugin* all that will be installed is the `hello-world-npm` package before the plugin is live.


# Scripting

## Plugin Walkthrough - 'hello-exchange' script.js

So to get started with the *hello-exchange* server script, first we are going to need to make the actual script that will be run:

1. Create a new file called `script.js`, in the same directory as the `config.json` (`plugins/src/plugins/hello-exchange/server/script.js`)
2. Fill this `script.js` with the code found in the box below.

<details>

<summary><em>hello-plugin</em>  <code>script.js</code> code</summary>

{% code lineNumbers="true" %}

```javascript
'use strict';

const { publicMeta, meta } = this.configValues;
const {
	app,
	loggerPlugin,
	toolsLib
} = this.pluginLibraries;
const helloWorld = require('hello-world-npm');
const moment = require('moment');

const init = async () => {
	loggerPlugin.info(
		'HELLO-PLUGIN PLUGIN initializing...'
	);

	if (!meta.private_message.value) {
		throw new Error('Configuration value private required');
	}
};

init()
	.then(() => {
		app.get('/plugins/hello-plugin/info', (req, res) => {
			loggerPlugin.verbose(
				req.uuid,
				'GET /plugins/hello-plugin/info'
			);

			return res.json({
				public_message: publicMeta.public_message.value,
				private_message: meta.private_message.value,
				library_message: helloWorld(),
				moment_timestamp: moment().toISOString(),
				exchange_info: toolsLib.getKitConfig().info
			});
		});
	})
	.catch((err) => {
		loggerPlugin.error(
			'HELLO-PLUGIN PLUGIN error during initialization',
			err.message
		);
	});
```

{% endcode %}

</details>

Check the screenshot below to compare and make sure you have the file in the correct location.&#x20;

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

## Calling the End Point

Now we have constructed '*hello-plugin*', let's call the endpoint. First, we will need to build the plugin.

1. If the terminals from earlier (when following how to set up dev mode) are no longer up, lets first get them going.&#x20;
   1. In one terminal navigate to *hollaex-kit/server* and run `docker-compose up`
   2. In the other,  enter the docker container with:  `docker exec -it server_hollaex-kit-server_1 /bin/bash`
2. With both the containers running build the plugin by running: `node plugins/dev.js --plugin=hello-plugin` inside the container (terminal we ran 'docker exec...' in)

Great news! This is all we had to do, and can now have a look at the plugin in the browser through the following URL:

<http://localhost:10013/plugins/hello-plugin/info>

You should see an output similar to below:

<figure><img src="/files/P9Yb46SCaz6fkRA9OVZ1" alt=""><figcaption><p>Chrome</p></figcaption></figure>

<figure><img src="/files/1LG8qIklAovfAvrL1icl" alt=""><figcaption><p>Firefox</p></figcaption></figure>


# Web View

Moving from the back-end to the front now, let's work on actually seeing our plugin in action

{% hint style="info" %}
The web view involves a few parts, here we will look at getting just our plugin working, but for the (very) detailed breakdown of how the front-end of plugins works and all the components that are utilized, check the ['Advanced/Web View' page](/plugins/develop-plugins/advanced/web-view).&#x20;
{% endhint %}

## Disabling CORS&#x20;

The first step we need to sort out is to disable CORs in the browser otherwise your web client does not communicate with the server.

At this point, Google Chrome is recommended. To disable CORS we have two choices:

1. Run the following in your terminal to open Chrome with CORS disabled:

   `google-chrome --user-data-dir="~/chrome-dev-disabled-security" --disable-web-security --disable-site-isolation-trials`
2. For an easier solution, install the following extension which can be used to disable CORs with ease: <https://webextension.org/listing/access-control.html>

With this done let's get started on developing the web view!

## Updating the Files

Now we are going to edit three files that are already inside the plugin (they have created automatically when we created the plugin initially). These files are:

1. `views.json` inside the `plugins/hello-plugin/views/view` directory.
2. `Form.js` in the same directory (`plugins/hello-plugin/views/view).`
3. `strings.json` inside the `plugins/hello-plugin/assets` directory.

### views.js

views.js only requires a single line to be changed, from its current placeholder value. The path is what needs to be changed to the hello plugin, like in the code below.

<details>

<summary>views.js</summary>

```json
{
  "meta": {
    "is_page": true,
    "path": "/hello-plugin",
    "hide_from_appbar": true,
    "hide_from_sidebar": false,
    "hide_from_menulist": false,
    "string": {
      "id": "title",
      "is_global": false
    },
    "icon": {
      "id": "SIDEBAR_HELP",
      "is_global": true
    }
  }
}
```

</details>

### Form.js

This will send a request to the endpoint that we have created and display exchange information to the user. Copy and paste the code in the block below.

<details>

<summary>Form.js</summary>

{% code title="Form.js" %}

```jsx
import React, { useEffect, useState } from 'react';
import { IconTitle, PanelInformationRow } from 'hollaex-web-lib';
import { withKit } from 'components/KitContext';
import Title from 'components/Title';
import axios from 'axios';

const Form = ({
  strings: STRINGS,
  icons: ICONS,
  generateId,
  plugin_url: PLUGIN_URL
}) => {

  const [info, setInfo] = useState();

  useEffect(() => {
    axios.get(`${PLUGIN_URL}/plugins/hello-plugin/info`).then(({ data: { exchange_info = {} }}) => {
      setInfo(exchange_info);
    })
  }, [])

  return (
    <div className="presentation_container apply_rtl verification_container">
      <IconTitle
        stringId={generateId('title')}
        text={STRINGS[generateId('title')]}
        textType="title"
        iconPath={ICONS['SIDEBAR_HELP']}
      />
      <form className="d-flex flex-column w-100 verification_content-form-wrapper">
        <div className="verification-form-panel mt-3 mb-5">
          <div className="my-4 py-4">
            <Title />
            <div className="py-4">
              {info ? Object.entries(info).map(([key, value]) => (
                <PanelInformationRow
                  key={key}
                  label={key}
                  information={value}
                  className="title-font"
                  disable
                />
              )) : (
                <div className="pt-4">Loading ...</div>
              )}
            </div>
          </div>
        </div>
      </form>
    </div>
  )
}

const mapContextToProps = ({ strings, activeLanguage, icons, generateId, plugin_url }) => ({
  strings,
  activeLanguage,
  icons,
  generateId,
  plugin_url
});

export default withKit(mapContextToProps)(Form);
```

{% endcode %}

</details>

### strings.js

Finally, update the title and content in the strings.json file, from its default to what's in the code block below:

```json
{
  "en": {
    "title": "Hello exchange",
    "hello": "Hello {0}"
  }
}
```

## Compiling and Starting the Plugin

With the above changes, we can now actually take a look at the plugin on the page!

First up we will need to run the plugin. As always make sure you are in dev-mode and have docker-compose up and running in a terminal

In another terminal, navigate to the *hollaex-kit/plugins* folder, and run the following:

```
npm start --plugin=hello-plugin
```

{% hint style="info" %}
If you receive the following error: `sh: 1: concurrently: not found`, this simply means the *concurrently* package isn't installed. Simply `run npm start --plugin=hello-plugin` inside the plugins folder, let it run, and then try the npm start command again.
{% endhint %}

This will begin the process and may take a little while but once you see the green '*Compiled Successfully!'* message, we can move on to actually seeing the plugin!

<figure><img src="/files/aXMy2DaoZnIYQAoXeQj2" alt=""><figcaption><p>Green = Good</p></figcaption></figure>

## Seeing the Plugin

Now for the moment of truth. Once the plugin has been compiled successfully, the browser may open to localhost:3000 automatically. If not automatically navigate to it yourself:&#x20;

{% embed url="<http://localhost:3000/>" %}

If everything has worked thus far, you should see a landing screen like the one below. This is our development-ready exchange!

You will need to make an account to access it, so do this and then log in.

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

Upon entering the exchange, you may notice something new on the sidebar (also in the screenshots, the exchange I set up was for the testnet and so looks a little different from the conventional HollaEx main screen).

<figure><img src="/files/3lUmuVmTcbtBLkrISQhu" alt=""><figcaption><p>See if you can spot what we are looking for 👀</p></figcaption></figure>

In the sidebar, we now have a new option '*Hello plugin*' which is unsurprisingly the 'Hello Plugin' that we have been developing for the last few pages! Click on it and we will see the results of all our hard work.

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

Now it main not be the most exciting page in the world, but hopefully, this goes to show the foundation of plugin development and is the first stepping stone that will get you going toward more exciting and unique plugin ideas!


# The Final Product & Installation

With the back and front of our plugin created over the last few pages, we need one final file, that we will use to actually put onto our real exchange. This file takes the form of a JSON file, hello-plugin.json

## Creating the file

All we have to do is run one command in the correct location, fomr your terminal:

1. Enter the hollaex-kit/plugins directory
2. Run: npm run build --plugin=hello-plugin
3. Let it run!

<figure><img src="/files/ngYBaRk0Q2wqG1ns1dZG" alt=""><figcaption><p>Section of nice, green code printed while the plugin was getting built</p></figcaption></figure>

This will take a few seconds and once complete there should be two new files located at:&#x20;

*hollaex-kit/plugins/src/plugins/hello-plugin/server*

<figure><img src="/files/ywU5xyLSrIsPo7F3Lrwf" alt=""><figcaption><p>hello-plugin.json and web-view.json are our newly built files</p></figcaption></figure>

## Using the JSON Files and Installing the Plugin

At this point, you may have been working on a test exchange but we will be wanting to use it on a real user exchange. To this end, all we need to do is take that JSON file we just built in the previous section and follow the next few steps.

Navigate to the Operator Controls of the exchange you want to install hello-plugin on, then navigate to the plugin screen as seen in the image below.

<figure><img src="/files/v8yS1MPnW7JfvINZvgcf" alt=""><figcaption><p>The plugin home screen in the op. controls</p></figcaption></figure>

Hit the green 'ADD THIRD PARTY PLUGIN' button in the top right. This will open the box as seen in the image below. Click the upload link, and find the hello-plugin.json file we built in the previous section.

You will be asked to confirm your new plugin, by entering 'I UNDERSTAND'.

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

The plugin is installed on the exchage! We will have hte option to tinker with it as in the image below. For hello-plugin, we can simply change the `public_message` and the `private_message`, and also if we choose to update the plugin, simply rebuild our new updated JSON, and upload with the 'Manually update' button.

<figure><img src="/files/E8wMImnHkXvDxdrLgFBE" alt=""><figcaption><p>We did it!</p></figcaption></figure>

Jumping out of the Operator Controls, give your exchange a refresh, and as if by magic, we will now see hello-plugin added to our sidebar, and our exchange!

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


# Advanced

In this section you can find more advance code for server scripts that allows you to add more complex logic.

* [Using the user meta field](/plugins/develop-plugins/advanced/advanced-tutorial-using-the-user-meta-field)
* [Adding a new database table column](/plugins/develop-plugins/advanced/advanced-tutorial-adding-a-new-database-table-column)
* [Creating a new database table](/plugins/develop-plugins/advanced/advanced-tutorial-announcements-plugin)


# Initialization

The first steps in development are initializing the plugin, and defining the type of plugin through templates. At the bottom of the page, we will begin to follow a simple example

{% hint style="warning" %}
Make sure you run `npm install` in the `/plugins` directory in HollaEx Kit **before** any other steps.
{% endhint %}

To start the development process, you need to initialize the plugin template. This template includes the folder structure and some pre-defined configurations to facilitate the development experience.&#x20;

Below is a list of available plugin templates:

<table><thead><tr><th width="223">Type</th><th>Template Details</th></tr></thead><tbody><tr><td><code>page</code></td><td>adds a new page with customizable access from the side and top menus</td></tr><tr><td><code>verification-tab</code></td><td>adds a new verification tab to the user verification page</td></tr><tr><td><code>fiat-wallet</code></td><td>adds a deposit and withdraw page for a fiat currency</td></tr><tr><td><code>kyc</code></td><td>adds KYC tab to the user verification page</td></tr><tr><td><code>bank</code></td><td>adds bank verification tab to the user verification page</td></tr><tr><td><code>raw</code> </td><td>adds a template without initial meta object values</td></tr><tr><td><code>onramp</code></td><td>adds an on-ramp section for the fiat controls feature</td></tr><tr><td><code>app</code></td><td>adds an app view to the apps section tables</td></tr><tr><td><code>server</code></td><td>adds a template without any view (Server-only)</td></tr></tbody></table>

## Initializing the Plugin Template

1. Once you decide on the type of plugin, go to `/plugins` and run `npm run add:plugin --plugin=<PLUGIN_NAME> --type=<PLUGIN_TYPE>` to initialize the plugin template.&#x20;

```
npm run add:plugin --plugin=<PLUGIN_NAME> --type=<PLUGIN_TYPE>
```

This will create a folder named the plugin's name in the `/plugins`  folder.

### Moving Forward - Plugin Components

In order to develop a plugin, we need to understand all the main components of a plugin. These main components of any HollaEx plugin will be looked at in the next sections.

1. [**Plugin config**](/plugins/develop-plugins/advanced/config)
2. [**Server script**](/plugins/develop-plugins/advanced/server-script)
3. [**Client Web View**](/plugins/develop-plugins/advanced/web-view)
4. **Plugin JSON**


# Config

The config.json defines the plugin's general structure. Here we will take a look at what makes up a config file and see how we make one for hello-exchange

The general structure of the plugin is defined in the `config.json` file. Config includes the plugin name, description, icon, etc.

The Config is basically a draft of a plugin JSON- the final product. Most items in the config are self-explanatory but below they are fully defined.

When the plugin is initialized, the config.json file will not be created, so it must be added manually. Check the *hello-exchange* plugin walkthrough at the bottom to see how this process is generally done.

<details>

<summary>Click here for an example of how a config.json looks</summary>

```json
{
	"name": "Plugin Name",
	"version": 1,
	"type": null,
	"author": "Author",
	"bio": "Short description shown in admin panel",
	"description": "Description shown on plugin page",
	"documentation": null,
	"logo": null,
	"icon": null,
	"url": null,
	"public_meta": {
		"public_value": {
			...
		}
	},
	"meta": {
		"private_value": {
			...
		}
	},
	"prescript": {
		"install": ["hello-world-npm"],
		"run": null
	},
	"postscript": {
		"run": null
	}
}
```

</details>

## Config File Elements

* **Name**: The name of your plugin must not include spaces and has to be unique from all other plugins installed on your Kit
* **Type**: The type of this plugin. This value can be set to `null`. If this value is set, no other plugin with the same `type` can be installed.
* **Version**: The version of your plugin is important when it comes to upgrading.&#x20;
* **Bio**: The bio is a short description of your plugin that shows in the plugin list on the blue admin panel
* **Description**: The description is the full description of your plugin shown on the plugin page.
* **Author**: The author who is shown on the plugin page.
* **Public\_meta**: The public\_meta object holds all public values used in the plugin that can be changed while the plugin is running. It should hold values that are publicly available.
* **Meta**: The meta-object holds all private values used in the plugin that can be changed while the plugin is running. It should hold values for unique keys, secrets, etc.&#x20;

<details>

<summary>Note on Public_meta &#x26; Meta and their requirements</summary>

`public_meta` and `meta` are variables accessible in the plugin interface on the exchange operator control and they can easily be dynamically set by the exchange operator.&#x20;

Variables like keys should be set in `meta` and variables that you want your public client web view to access should be set in `public_meta` .

Both the `public_meta` and `meta` objects **require** the keys `type`, `required`, `description`, and `value` for each parameter added.&#x20;

* `type` is the data type of the parameter (only `number`, `string`, `boolean`, and `date-time` are allowed).&#x20;
* `required` is whether or not the parameter is required for the plugin.
* `description` is the description of the parameter in use.
* `value` is the actual value set for the plugin.

</details>

* **Prescript**: The prescript object holds two fields, `install`, and `run`. `install` is an array of strings. Each string is the name of the NPM library the plugin should install before running. `run` is a bash script run before the plugin is enabled. Currently, the `run` feature is not enabled.

<details>

<summary>Note on Prescript</summary>

`prescript` is a set of scripts that should run before the plugin goes live. Currently, it supports `install` which is an array of npm libraries you want to install.&#x20;

Note that you do not need to install HollaEx Kit dependencies since they are already available by default.

</details>

* **Postscript**: The postscript object holds the field `run`. `run` is a bash script that runs after the plugin is enabled. Currently, the `run` feature is not enabled.
* **Script**: This is the ES6+ script for your plugin. When enabled, this script will run. The script should be passed as a minified string.
* **Web\_view**: This field will contain the plugin's web client code. You can read more about it in the later *Web View Development* page [here](/plugins/develop-plugins/web-view-development).
* **Admin\_view**: This field will contain the plugin's admin client code. This is HTML code and once added to the plugin there will be a new section added to the left sidebar of the operator control that includes this code. This is used for cases where the admin wants to have more freedom and control over certain actions beyond just configuring `meta` and `public_meta`

To see an example of how these elements are used in a real plugin example check the *hello-exchange* walkthrough below.

**To continue with the next section** on how to make endpoints and server-side coding, [click here](/plugins/develop-plugins/advanced/server-script).

##


# Server Script

The script described here provides the back-end of our plugin

The server script, `script.js` defines what libraries are imported and is what will get us to our end goal

## Before You Start - **Dev Mode**

Before you start, make sure you have an exchange running in [dev mode](/developers/run-dev-mode) on your machine. It is important to have the exchange running in dev mode instead of a standard production CLI setup.

Dev mode exchange has live code updates and any changes in the code will get immediately applied which will make your development experience much better as a developer.

## Create the Script

Looking inside the `plugins/src/plugins/<YOUR_PLUGIN>` you will see that the [initialization](/plugins/develop-plugins/advanced/initialization) created a server folder for us. This is the folder where the `config.json` was made.

1. Create a new file in this server folder called `script.js`&#x20;
2. Code `script.js` to whatever goal you have in mind for the plugin (I recommended here looking below at the hello-exchange walkthrough to get an idea of how a script can look)&#x20;

`script.js` is the server-side code that runs during runtime into the server's plugin container. The server script has access to many [libraries](#plugin-libraries) available in the HollaEx Kit.&#x20;

The most important library of all is the [HollaEx Tools Library](https://github.com/hollaex/hollaex-kit/tree/master/server/utils/hollaex-tools-lib) which has all the main features you need.

{% hint style="info" %}
If at any point, you become unsure of the structure of either your plugin's folders or the code within the files themselves, take a look at the [*hello-exchange*](https://github.com/hollaex/hollaex-kit/tree/master/plugins/src/plugins/hello-exchange) example on GitHub to see how plugin structure should be organized.
{% endhint %}

## Run the Script

{% hint style="warning" %}
Ensure you have executed `npm install` before running the command below.&#x20;

You may also need to add `sudo` at the beginning of the command above depending on your OS.

**In addition**, make sure **CORS has been disabled** on your browser (this can be tricky in Firefox, so Chrome may be easier to work with).command

Finally, for the run comand to work, you will need python 2 on your machine, this can be installed for Linux with sudo apt install python
{% endhint %}

To run the script on your dev mode exchange, you need to go to `/plugins` and run:

```
npm start --plugin=<PLUGIN_NAME>
```

{% hint style="info" %}
Remember this will need to be done in a **new** **terminal** as dev mode will block any input on the terminal it was started on
{% endhint %}

To see if this has worked try accessing localhost:8080/config.json, and you will see the config file of the plugin


# Plugin Libraries

Libraries allow access to functionality for plugins. Some are included within the HollaEx Kit, but you have the ability to import ones of your own choosing that have not been included

There are three types of third-party libraries for all plugins: *preconfigured*, *default*, and *plugin specific*.

### Preconfigured Libraries

Preconfigured libraries are libraries that have the same configuration for all plugins. These are included in an object `this.pluginLibraries`. The libraries included are:

* [`app`](https://www.npmjs.com/package/express) - Express app (v4.16.2)
* [`toolsLib`](https://github.com/bitholla/hollaex-tools-lib) - HollaEx Tools Library
* [`loggerPlugin`](https://www.npmjs.com/package/winston) - Winston logger (v3.2.1)

### Default Libraries

Default libraries are libraries that are already installed in the Kit itself. These can be imported using `require`. Please take a look at the Kit [`package.json`](https://github.com/bitholla/hollaex-kit/blob/master/server/package.json) file for all default libraries included. Some are:

<details>

<summary>Click here to view the default libraries</summary>

* [`lodash`](https://www.npmjs.com/package/lodash) - (v4.17.20)
* [`expressValidator`](https://www.npmjs.com/package/express-validator) - (v6.7.0)
* [`multer`](https://www.npmjs.com/package/multer) - (v1.4.2)
* [`moment`](https://www.npmjs.com/package/moment) - (v2.21.0)
* [`mathjs`](https://www.npmjs.com/package/mathjs) - (v3.20.2)
* [`bluebird`](https://www.npmjs.com/package/bluebird) - (v3.5.3)
* [`rp`](https://www.npmjs.com/package/request-promise) - Request promise (v4.2.2)
* [`uuid`](https://www.npmjs.com/package/uuid) - (v3.2.1)
* [`umzug`](https://www.npmjs.com/package/umzug) - (v2.3.0)
* [`sequelize`](https://www.npmjs.com/package/sequelize) - (v4.37.7)
* [`json2csv`](https://www.npmjs.com/package/json2csv) - (v4.5.4)
* [`flat`](https://www.npmjs.com/package/flat) - (v5.0.0)
* [`cron` ](https://www.npmjs.com/package/node-cron) - (v2.8.5)
* [`bcryptjs`](https://www.npmjs.com/package/bcryptjs) - (v2.4.3)
* [`validator`](https://www.npmjs.com/package/validator) - (v9.4.1)
* [`cors`](https://www.npmjs.com/package/cors) - (v2.8.5)

</details>

### Plugin Specific Libraries

Plugin-specific libraries are libraries that are not installed in the Kit but are required for the plugin. These can be installed through the `prescript.install` object in the plugin `config.json file` and imported using `require`.&#x20;

{% hint style="danger" %}
**Do not add** any plugin-specific libraries that are already included by default.&#x20;

A different version could be installed which can cause unexpected bugs.
{% endhint %}

To add a plugin-specific library on installation, include the library name inside the `install` array in the  `prescript` object. To specify a version, include the `@` symbol with the version desired (similar to how a basic npm install works).

```javascript
{
    ...
    prescript: {
        install: ['hello-world-npm']
    },
    ...
```


# Web View

For the user the plugin isn't useful if they can't see much, this page discusses how we create the front-end of the plugin

Web views are remote React components and are injected into the kit on the fly. HollaEx uses a package called [remote component](https://github.com/Paciolan/remote-component) to get a component by providing the URL of the commonJS module bundle. These components are added to the kit using the [SmartTarget](https://github.com/bitholla/hollaex-kit/blob/master/web/src/components/SmartTarget/index.js) component in the Hollaex web kit.

## SmartTarget Component

To decide where to inject remote components, we are using the [SmartTarget](https://github.com/bitholla/hollaex-kit/blob/master/web/src/components/SmartTarget/index.js) component in the kit.

A SmartTarget is actually a React component with a unique target id that renders a remote bundle when the id matches.

Smart targets are also responsible for passing props to the remote components. These props are divided into two different categories: common props and target-specific props.

## Common Props

Common props are passed to all remote components within the smart target component. They include but are not limited to strings, icons, generateId function, renderFields function to generate forms, store values, edit and config context. You can always check the latest available common props for remote components by checking the [SmartTarget](https://github.com/bitholla/hollaex-kit/blob/master/web/src/components/SmartTarget/index.js) component code.

## Target Specific Props

Target-specific props are passed to the smart target component from its parent component and may be different for each target. To get the latest available target-specific props, you can check the parent component of each SmartTarget component. We can use these props using the kit context. See the [kit context](https://app.gitbook.com/s/-MP899VqAdyGFgLTy9SY/c/TNYMLpvJMY9m1wha1UiK/how-tos/web-view-development/getting-started) section for more details.

* [New Page](https://github.com/bitholla/hollaex-kit/blob/master/web/src/routes.js#L240)
* Fiat Wallet
  * [Deposit](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Deposit/utils.js#L178)
  * [Withdraw](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Withdraw/form.js#L265)
* New Verification Tab
  * [Home](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Verification/index.js#L234)
  * [Page Content](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Verification/index.js#L256)
* Bank Verification Tab
  * [Home](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Verification/index.js#L355)
  * [Page Content](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Verification/index.js#L506)
* KYC Verification Tab
  * [Home](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Verification/index.js#L380)
  * [Page Content](https://github.com/bitholla/hollaex-kit/blob/master/web/src/containers/Verification/index.js#L523)

## View types

There are two types of views in terms of the target field, static target view and dynamic target view.

#### Static Targets

Static targets are hard-coded in the Hollaex web kit and views are injected based on these targets. See the web\_view array section for more information.

* **Verification Page Bank Tab**
  * REMOTE\_COMPONENT\_\_BANK\_VERIFICATION
  * REMOTE\_COMPONENT\_\_BANK\_VERIFICATION\_HOME
* **Verification Page KYC Tab**
  * REMOTE\_COMPONENT\_\_KYC\_VERIFICATION
  * REMOTE\_COMPONENT\_\_KYC\_VERIFICATION\_HOME

#### Dynamic Targets

Dynamic targets are generated on the Hollaex web kit before the injection. They are created based on the meta object values.

* New Page
* New verification tab
* Fiat wallet deposit and withdrawal page

## Web\_view array

Web\_view is an array of objects. Each object in the web\_view array corresponds to a view of the plugin. The basic view structure is in the form of a JSON object.

```json
{
    src: "string",
    target: "string",
    meta: "object",
    is_default: "bool",
    injected_html: {
        head: "string",
        body: "string"
    }
}
```

Let's go over each key in this JSON object.

### Src

The src is the address of the view bundle file.

### Target

Target is the id of the view in static target views. The target is used to inject the view into the corresponding SmartTarget.

### Is\_default

The is\_default value specifies if a view is the default view.

### Injected\_html

Holds HTML strings for the head and the body. These values will be injected into the DOM. This field can be used to add script tags.

### Meta

The meta object holds strings, icons, and some essential fields to define each plugin type for dynamic target views.

## Meta Object

Below you can see essential fields to define each dynamic plugin type. These values should be added to the meta object under the view\.json file to define the type of the plugin. These valuse are already set when you are using plugin starter templates.

### **New page:**

```json
{
    "meta": {
        "is_page": true,
        "path": "/route-name"
    }
}
```

### **New verification tab:**

```json
{
    "meta": {
        "is_verification_tab": true,
        "type": "home" or "verification",
    }
}
```

### **Fiat Wallet**

```json
{
    "meta": {
        "is_wallet": true,
        "type": "deposit" or "withdraw",
        "currency": "USD" /* currency symbol /*
     }
}
```

## External Dependancies

The plugin web view bundle is self-contained with all of its dependencies bundled with the webpack. In order to optimize the bundle size, the following dependencies are provided as externals. These dependencies will not be included in the bundle. The web kit is expected to provide these dependencies.

* [@ant-design/icons](https://www.npmjs.com/package/@ant-design/icons) - (v4.2.2)
* [antd](https://www.npmjs.com/package/antd) - (v4.6.2)
* [axios](https://www.npmjs.com/package/axios) - (v0.21.1)
* [classnames](https://www.npmjs.com/package/classnames) - (v2.2.6)
* [hollaex-web-lib](https://www.npmjs.com/package/hollaex-web-lib) - (v0.3.0)
* [mathjs](https://www.npmjs.com/package/mathjs) - (v5.10.3)
* [moment](https://www.npmjs.com/package/moment) - (v2.24.0)
* [numbro](https://www.npmjs.com/package/numbro) - (v1.11.1)
* [prop-types](https://www.npmjs.com/package/prop-types) - (v15.7.2)
* [react](https://www.npmjs.com/package/react) - (v16.13.1)
* [react-device-detect](https://www.npmjs.com/package/react-device-detect) - (v1.6.2)
* [react-event-listener](https://www.npmjs.com/package/react-event-listener) - (v0.6.6)
* [react-redux](https://www.npmjs.com/package/react-redux) - (v6.0.1)
* [react-svg](https://www.npmjs.com/package/react-svg) - (v11.2.2)
* [redux](https://www.npmjs.com/package/redux) - (v4.0.1)
* [redux-form](https://www.npmjs.com/package/redux-form) - (v8.1.0)
* [validator](https://www.npmjs.com/package/validator) - (v10.11.0)

Web views are remote react components and are injected into the kit on the fly. We are using a package called [remote component](https://github.com/Paciolan/remote-component) to get a component by providing the URL of the commonJS module bundle. These components are added to the kit using the [SmartTarget](https://github.com/bitholla/hollaex-kit/blob/master/web/src/components/SmartTarget/index.js) component in the Hollaex web kit.

## SmartTarget component

To decide where to inject remote components, we are using the [SmartTarget](https://github.com/bitholla/hollaex-kit/blob/master/web/src/components/SmartTarget/index.js) component in the kit.

A SmartTarget is actually a react component with a unique target id that renders a remote bundle when the id matches.

Smart targets are also responsible for passing props to the remote components. These props are divided into two different categories: common props and target-specific props.

## Common props

Common props are passed to all remote components within the smart target component. They include but are not limited to strings, icons, generateId function, renderFields function to generate forms, store values, edit and config context. You can always check the latest available common props for remote components by checking the [SmartTarget](https://github.com/bitholla/hollaex-kit/blob/master/web/src/components/SmartTarget/index.js) component code.

## Target specific props

Target-specific props are passed to the smart target component from its parent component and may be different for each target. To get the latest available target-specific props, you can check the parent component of each SmartTarget component. We can use these props using the kit context. See the [kit context](#step-7-using-the-kit-context) section for more details.

* [New Page](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/routes.js#L264)
* Fiat Wallet
  * [Deposit](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Deposit/Fiat/index.js#L9)
  * [Withdraw](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Withdraw/Fiat/index.js#L9)
* New Verification Tab
  * [Home](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Verification/index.js#L302)
  * [Page Content](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Verification/index.js#L324)
* Bank Verification Tab
  * [Home](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Verification/index.js#L423)
  * [Page Content](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Verification/index.js#L598)
* KYC Verification Tab
  * [Home](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Verification/index.js#L448)
  * [Page Content](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Verification/index.js#L614)
* [Onramp](https://github.com/hollaex/hollaex-kit/blob/b3ad844854509db54475551cc3452519e824d09a/web/src/containers/Deposit/Fiat/Form.js#L92)
* App
  * [Kit](https://github.com/hollaex/hollaex-kit/blob/ea79e55d628522b3d31b0170760f6c5ee994ced3/web/src/containers/AppDetails/index.js#L64)
  * [Admin](https://github.com/hollaex/hollaex-kit/blob/ea79e55d628522b3d31b0170760f6c5ee994ced3/web/src/containers/Admin/Apps/index.js#L85)

## View types

There are two types of views in terms of the target field, static target view and dynamic target view.

#### Static Targets

Static targets are hard-coded in the Hollaex web kit and views are injected based on these targets. See the web\_view array section for more information.

* **Verification Page Bank Tab**
  * REMOTE\_COMPONENT\_\_BANK\_VERIFICATION
  * REMOTE\_COMPONENT\_\_BANK\_VERIFICATION\_HOME
* **Verification Page KYC Tab**
  * REMOTE\_COMPONENT\_\_KYC\_VERIFICATION
  * REMOTE\_COMPONENT\_\_KYC\_VERIFICATION\_HOME

#### Dynamic Targets

Dynamic targets are generated on the Hollaex web kit before the injection. They are created based on the meta object values.

* New Page
* New verification tab
* Fiat wallet deposit and withdrawal page
* Onramp section
* App section

## Web\_view array

Web\_view is an array of objects. Each object in the web\_view array corresponds to a view of the plugin. The basic view structure is in the form of a JSON object.

```json
{
    src: "string",
    target: "string",
    meta: "object",
    is_default: "bool",
    injected_html: {
        head: "string",
        body: "string"
    }
}
```

Let's go over each key in this JSON object.

### Src

The src is the address of the view bundle file.

### Target

Target is the id of the view in static target views. The target is used to inject the view into the corresponding SmartTarget.

### Is\_default

The is\_default value specifies if a view is the default view.

### Injected\_html

Holds HTML strings for the head and the body. These values will be injected into the DOM. This field can be used to add script tags.

### Meta

The meta object holds strings, icons, and some essential fields to determine each plugin type for dynamic target views.

## Meta Object

Below you can see essential fields to define each dynamic plugin type. These values should be added to the meta object under the view\.json file to determine the type of the plugin. These values are already set when you are using plugin starter templates.

### **New page:**

```json
{
    "meta": {
        "is_page": true,
        "path": "/route-name"
    }
}
```

### **New verification tab:**

```json
{
    "meta": {
        "is_verification_tab": true,
        "type": "home" or "verification",
    }
}
```

### **Fiat Wallet**

```json
{
    "meta": {
        "is_wallet": true,
        "type": "deposit" or "withdraw",
        "currency": "USD" /* currency symbol /*
     }
}
```

### **Onramp**

```json
{
    "meta": {
        "is_ultimate_fiat": true,
        "type": "onramp"
     }
}
```

### **App**

```json
{
    "meta": {
        "is_app": "true",
        "type": "kit" or "admin",
     }
}
```

## External dependencies

The plugin web view bundle is self-contained, with all its dependencies bundled with the webpack. In order to optimize the bundle size, the following dependencies are provided as externals. These dependencies will not be included in the bundle. The web kit is expected to provide these dependencies.

* [@ant-design/icons](https://www.npmjs.com/package/@ant-design/icons) - (v4.2.2)
* [antd](https://www.npmjs.com/package/antd) - (v4.6.2)
* [axios](https://www.npmjs.com/package/axios) - (v0.21.1)
* [classnames](https://www.npmjs.com/package/classnames) - (v2.2.6)
* [hollaex-web-lib](https://www.npmjs.com/package/hollaex-web-lib) - (v0.3.0)
* [mathjs](https://www.npmjs.com/package/mathjs) - (v5.10.3)
* [moment](https://www.npmjs.com/package/moment) - (v2.24.0)
* [numbro](https://www.npmjs.com/package/numbro) - (v1.11.1)
* [prop-types](https://www.npmjs.com/package/prop-types) - (v15.7.2)
* [react](https://www.npmjs.com/package/react) - (v16.13.1)
* [react-device-detect](https://www.npmjs.com/package/react-device-detect) - (v1.6.2)
* [react-event-listener](https://www.npmjs.com/package/react-event-listener) - (v0.6.6)
* [react-redux](https://www.npmjs.com/package/react-redux) - (v6.0.1)
* [react-svg](https://www.npmjs.com/package/react-svg) - (v11.2.2)
* [redux](https://www.npmjs.com/package/redux) - (v4.0.1)
* [redux-form](https://www.npmjs.com/package/redux-form) - (v8.1.0)
* [validator](https://www.npmjs.com/package/validator) - (v10.11.0)

## Developing views

### Using the kit context

We can always directly use props passed from the kit to the remote component. However, to prevent passing some props through many levels, a context is provided to make these props globally accessible. You can partially subscribe to the context to access props from the kit in a more efficient way.

```jsx
import React from "react";
import { withKit } from 'components/KitContext';

const Title = ({ user: { username } = {}, strings: STRINGS, generateId }) => (
  <div className="secondary-text">
    {STRINGS.formatString(STRINGS[generateId('hello')], username)}
  </div>
);

const mapContextToProps = ({ user, generateId, strings }) => (
{user, generateId, strings}
);

export default withKit(mapContextToProps)(Title);
```

### Using strings and icons

We can use strings and icons from the main kit.

We also can define new strings and icons by adding them to strings.json and icons.json under the assets folder respectively.

These values are added to the kit strings and icons object during kit initialization. To use local assets in your component, you should convert the local id to the global one by using the generateId function from the kit context.

Consider we have the following strings.json file.

```json
{
  "en": {
    "title": "Hello exchange"
  }
}
```

To use the `title` string, we need to make the string key global using the `generateId` function. This function is globally accessible in the kit context.

```javascript
const globalTitleId = generateId('title');
```

Then we can get the string from the global strings object using the above-mentioned global id. The global strings object is also available in the kit context.

```javascript
const titleString = STRINGS[globalTitleId];
```

##


# Final Plugin Product

After all the work our output will be surprisingly simple and easy to work with

The final product of your plugin is a JSON file that can easily be [installed](/plugins/installing-plugin) in HollaEx Kit in the exchange operator controls, following the third-party plugin installation process.&#x20;

This JSON file includes all the information required for the plugin to run on the server. Once installed on your exchange, the server runs the script on its backend and loads the web view to smart targets in the web app.

## Final JSON

When you run the plugin in dev mode it automatically builds the plugin JSON so all you need to do is to copy `hello-exchange.json` file in `/plugins/src/plugins/hello-exchange/server/hello-exchange.json` file and [install](/plugins/installing-plugin) it to your exchange operator control.

You can find the final hello-exchange plugin below. The final file must be in JSON format.

{% file src="/files/KbXLmKmr9Q1I6TqTfdns" %}


# Advanced Tutorial: Using the user meta field

In this tutorial, we will create a plugin that allows an admin to check the number of trades a user has made. Once checked, the plugin will store the timestamp of the last trade completed by the user in `meta`. The next time the plugin checks the user's trade amount, it will only count trades made after the user's stored trade timestamp.

We will focus on the `script` section for this plugin.

{% hint style="info" %}
The user `meta` field is a JSON object in the User DB model that we can use to add additional fields for users.&#x20;
{% endhint %}

## Script

```javascript
'use strict';

const {
	app,
	loggerPlugin,
	toolsLib
} = this.pluginLibraries;
const { query, validationResult } = require('express-validator');
const moment = require('moment');

const USER_META_FIELD_NAME = 'trade_count_plugin-latest_trade';

const init = async () => {
	loggerPlugin.info(
		'PLUGIN TRADE COUNT initializing...'
	);

	if (!toolsLib.getKitConfig().user_meta[USER_META_FIELD_NAME]) {
		loggerPlugin.verbose(
			'PLUGIN TRADE COUNT',
			`User meta field ${USER_META_FIELD_NAME} not found. Creating field.`
		);

		await toolsLib.addKitUserMeta(
			USER_META_FIELD_NAME,
			'date-time',
			'Last trade timestamp used for trade-count plugin',
			false
		);
	}

	loggerPlugin.info(
		'PLUGIN TRADE COUNT initialized'
	);
};

init()
	.then(() => {
		app.get(
			'/plugins/trade-count/check',
			[
				toolsLib.security.verifyBearerTokenExpressMiddleware(['admin']),
				query('user_id').isInt({ min: 1 }).toInt().optional()
			],
			async (req, res) => {
				const errors = validationResult(req);
				if (!errors.isEmpty()) {
					return res.status(400).json({ errors: errors.array() });
				}

				const { user_id } = req.query;

				loggerPlugin.verbose(
					req.uuid,
					'GET /plugins/trade-count/check auth',
					req.auth.sub,
					'user_id:',
					user_id
				);

				try {
					const user = await toolsLib.user.getUserByKitId(user_id, false);

					if (!user) {
						throw new Error('User not found');
					}

					const lastTradeTimestamp = user.meta[USER_META_FIELD_NAME];
					const queryStartDate = toolsLib.isDatetime(lastTradeTimestamp)
						? moment(lastTradeTimestamp).add(1, 'ms').toISOString()
						: null;

					loggerPlugin.verbose(
						req.uuid,
						'GET /plugins/trade-count/check',
						'user last trade timestamp',
						lastTradeTimestamp,
						'trades query start date',
						queryStartDate
					);

					const trades = await toolsLib.order.getAllUserTradesByKitId(
						user.id,
						null,
						1,
						1,
						'timestamp',
						'desc',
						queryStartDate
					);

					loggerPlugin.verbose(
						req.uuid,
						'GET /plugins/trade-count/check',
						'trade count',
						trades.count
					);

					if (trades.count === 0) {
						return res.json({
							count: 0
						});
					}

					const lastTrade = trades.data[0];

					loggerPlugin.verbose(
						req.uuid,
						'GET /plugins/trade-count/check',
						'last trade timestamp',
						lastTrade.timestamp
					);

					await user.update({
						meta: {
							...user.meta,
							[USER_META_FIELD_NAME]: lastTrade.timestamp
						}
					});

					return res.json({
						count: trades.count
					});

				} catch (err) {
					loggerPlugin.error(
						req.uuid,
						'GET /plugins/trade-count/check err',
						err.message
					);

					return res.status(400).json({ message: err.message });
				}
			}
		);
	})
	.catch((err) => {
		loggerPlugin.error(
			'PLUGIN TRADE COUNT err during initialization',
			err.message
		);
	});

```

## Breakdown

### Import Requirements

```javascript
const {
	app,
	loggerPlugin,
	toolsLib
} = this.pluginLibraries;
const { query, validationResult } = require('express-validator');
const moment = require('moment');
```

First, import all the libraries that are required for this plugin.

### Set meta field name

```javascript
const USER_META_FIELD_NAME = 'trade_count_plugin-latest_trade';
```

All user `meta` fields require a name. Here, we are calling the `meta` field `trade_count_plugin-latest_trade`. We recommend formatting `meta` field names for plugins as `<PLUGIN_NAME>-<FIELD_NAME>`

### Check if meta field exists in init

```javascript
const init = async () => {
	loggerPlugin.info(
		'PLUGIN TRADE COUNT initializing...'
	);

	if (!toolsLib.getKitConfig().user_meta[USER_META_FIELD_NAME]) {
		loggerPlugin.verbose(
			'PLUGIN TRADE COUNT',
			`User meta field ${USER_META_FIELD_NAME} not found. Creating field.`
		);

		await toolsLib.addKitUserMeta(
			USER_META_FIELD_NAME,
			'date-time',
			'Last trade timestamp used for trade-count plugin',
			false
		);
	}

	loggerPlugin.info(
		'PLUGIN TRADE COUNT initialized'
	);
};

init()
	.then(() => {...})
	.catch((err) => {
		loggerPlugin.error(
			'PLUGIN TRADE COUNT err during initialization',
			err.message
		);
	});
```

Next, we create an `init` function that creates the new user `meta` field if it doesn't already exist. All user `meta` fields can be found in the Kit config object `user_meta`. Each field in `user_meta` has a `type`, `required` and `description` value.

```json
// KIT CONFIG

{
    ...,
    "user_meta": {
        "trade_count_plugin-latest_trade": {
            "type": "date-time",
            "required": false,
            "description": "Last trade timestamp used for trade-count plugin"
        }
    },
    ...
}
```

We are first checking if the user `meta` field exists in the Kit config returned from `toolsLib.getKitConfig`. If not, we are creating the new field using the tools library function `toolsLib.addKitUserMeta`. Please refer to the tools library documentation for more information regarding the tools library. Once initialized, the main script will be executed.

### Create an endpoint for counting user trades

```javascript
app.get(
	'/plugins/trade-count/check',
	[
		toolsLib.security.verifyBearerTokenExpressMiddleware(['admin']),
		query('user_id').isInt({ min: 1 }).toInt().optional()
	],
	async (req, res) => {
		const errors = validationResult(req);
		if (!errors.isEmpty()) {
			return res.status(400).json({ errors: errors.array() });
		}
		
		const { user_id } = req.query;

		loggerPlugin.verbose(
			req.uuid,
			'GET /plugins/trade-count/check auth',
			req.auth.sub,
			'user_id:',
			user_id
		);
```

For this plugin, we need an endpoint `GET /plugins/trade-count/check` that returns the number of trades a user has made.&#x20;

```javascript
try {
	const user = await toolsLib.user.getUserByKitId(user_id, false);

	if (!user) {
		throw new Error('User not found');
	}

	const lastTradeTimestamp = user.meta[USER_META_FIELD_NAME];
	const queryStartDate = toolsLib.isDatetime(lastTradeTimestamp)
		? moment(lastTradeTimestamp).add(1, 'ms').toISOString()
		: null;

	loggerPlugin.verbose(
		req.uuid,
		'GET /plugins/trade-count/check',
		'user last trade timestamp',
		lastTradeTimestamp,
		'trades query start date',
		queryStartDate
	);

	const trades = await toolsLib.order.getAllUserTradesByKitId(
		user.id,
		null,
		1,
		1,
		'timestamp',
		'desc',
		queryStartDate
	);

	loggerPlugin.verbose(
		req.uuid,
		'GET /plugins/trade-count/check',
		'trade count',
		trades.count
	);

	if (trades.count === 0) {
		return res.json({
			count: 0
		});
	}

	const lastTrade = trades.data[0];

	loggerPlugin.verbose(
		req.uuid,
		'GET /plugins/trade-count/check',
		'last trade timestamp',
		lastTrade.timestamp
	);

	await user.update({
		meta: {
			...user.meta,
			[USER_META_FIELD_NAME]: lastTrade.timestamp
		}
	});

	return res.json({
		count: trades.count
	});

} catch (err) {
	loggerPlugin.error(
		req.uuid,
		'GET /plugins/trade-count/check err',
		err.message
	);

	return res.status(400).json({ message: err.message });
}
```

We are first checking to see if a user with the given `user_id` exists. If so, we check to see if the user's `meta.trade_count_plugin-latest_trade` value exists. If it doesn't that means we haven't checked the user's trade count using this plugin before. If it does, that means we have previously checked the user's trade count.

Using the found `trade_count_plugin-latest_trade` value, we can determine which date to query the user's trades from. If `trade_count_plugin-latest_trade` was `null`, then we will query all of the user's trades.

Once we get the trades response, we can read the number of trades made and the timestamp of the last trade completed. If the `count` is `0`, then we can just return `0` without updating the user's   `trade_count_plugin-latest_trade` value. Otherwise, we will get the latest trade's `timestamp`, update the user's `trade_count_plugin-latest_trade` value to that `timestamp`, and return the `count`. The next time we check the user's trade count, all trades starting from the updated `trade_count_plugin-latest_trade` will be counted.


# Advanced Tutorial: Adding a new database table column

In this tutorial, we will go over how to add a new column for an existing table by creating a new migration. To illustrate this, we are creating a plugin that allows an admin to set a list of requirements for tier levels. The requirements will be stored in a newly created column in the `Tier` table called `requirements`. This column will be created by a migration using the [`umzug`](https://www.npmjs.com/package/umzug) library.

{% hint style="danger" %}
Changes to the database can result in unexpected side effects. Please be careful when altering yo
{% endhint %}

## Script

```javascript
'use strict';

const {
	app,
	loggerPlugin,
	toolsLib
} = this.pluginLibraries;
const lodash = require('lodash');
const sequelize = require('sequelize');
const umzug = require('umzug');
const cron = require('node-cron');
const { body, validationResult } = require('express-validator');

const AVAILABLE_REQUIREMENTS = {
	kyc_verification: {
		title: 'KYC Verification',
		description: 'Require users to verify their identity'
	},
	email_verification: {
		title: 'Email Verification',
		description: 'Require users to verify their email'
	},
	sms_verification: {
		title: 'SMS Verification',
		description: 'Require users to input a valid phone number'
	},
	bank_verification: {
		title: 'Bank Verification',
		description: 'Require users to input a valid bank account and status is 3'
	}
};

const init = async () => {
	const umzugInstance = new umzug({
		storage: 'sequelize',
		storageOptions: {
			sequelize: toolsLib.database.getModel('sequelize'),
			modelName: 'PluginMigrations',
			tableName: 'PluginMigrations'
		},
		upName: 'up',
		downName: 'down',
		migrations: umzug.migrationsList(
			[
				{
					name: 'automatic_tier_upgrade-add_requirements_column',
					up: (queryInterface, Sequelize) => queryInterface.describeTable('Tiers')
						.then((table) => {
							if (table['requirements']) {
								return new Promise((resolve) => resolve());
							} else {
								return queryInterface.addColumn('Tiers', 'requirements', {
									type: Sequelize.JSONB,
									defaultValue: []
								});
							}
						}),
					down: (queryInterface, Sequelize) => queryInterface.describeTable('Tiers')
						.then((table) => {
							if (table['requirements']) {
								return queryInterface.removeColumn('Tiers', 'requirements');
							} else {
								return true;
							}
						})
				}
			],
			[toolsLib.database.getModel('sequelize').getQueryInterface(), sequelize]
		)
	});

	const pending = await umzugInstance.pending();
	if (pending.length > 0) {
		await umzugInstance.up('automatic_tier_upgrade-add_requirements_column');
	}
};

const findAllTiers = async () => {
	return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Tiers" ORDER BY id ASC', {
		raw: true,
		type: sequelize.QueryTypes.SELECT
	});
};

const findTier = async (id) => {
	return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Tiers" WHERE id = :id', {
		plain: true,
		raw: true,
		replacements: {
			id
		},
		type: sequelize.QueryTypes.SELECT
	});
};

const updateTier = async (id, requirements) => {
	const tier = await toolsLib.database.getModel('sequelize').query('UPDATE "Tiers" SET requirements = :requirements WHERE id = :id RETURNING *', {
		replacements: {
			id,
			requirements: JSON.stringify(requirements)
		}
	});

	return tier[0][0];
};

const validateTierUpdate = async (tier_id, data = []) => {
	const tiers = await findAllTiers();

	// if removing all requirements, check if upper requirements exist
	if (lodash.isEmpty(data)) {
		const isInvalid = tiers.some((tier) => {
			return tier.id > tier_id && !lodash.isEmpty(tier.requirements);
		});

		if (isInvalid) {
			throw new Error('Cannot remove requirements if a higher tier has requirements set');
		}

		return;
	}

	// check if lower tier has requirements set if level is 3 or above
	if (tier_id > 2) {
		const isInvalid = tiers.some((tier) => {
			return tier.id >= 2 && tier.id < tier_id && lodash.isEmpty(tier.requirements);
		});

		if (isInvalid) {
			throw new Error('Lower tiers must have requirements set');
		}
	}

	// check if given static requirements are already set for other tier levels

	const isInvalid = tiers.some((tier) => {
		const existingRequirements = lodash.intersection(data, tier.requirements);
		return tier.id !== tier_id && !lodash.isEmpty(existingRequirements);
	});

	if (isInvalid) {
		throw new Error('Static requirements can only be set for one tier');
	}
};

const runner = async () => {
	const tiers = await findAllTiers();

	const users = await toolsLib.database.findAll('user', {
		where: {
			activated: true,
			flagged: false
		},
		raw: true,
		attributes: [
			'id',
			'email',
			'phone_number',
			'id_data',
			'verification_level',
			'email_verified',
			'activated',
			'bank_account',
			'flagged'
		]
	});

	const groupedUsers = lodash.groupBy(users, 'verification_level');

	for (const level in groupedUsers) {
		const userLevel = parseInt(level);

		for (const user of groupedUsers[level]) {
			loggerPlugin.debug(
				'AUTO TIER UPGRADE PLUGIN',
				`Checking verifications for user ${user.email} with level ${userLevel}`
			);

			let updatedLevel = userLevel;

			const checkedTiers = tiers.filter((tier) => tier.id >= 2 && tier.id > userLevel);

			const userVerifications = [];

			if (user.id_data.status === 3) {
				userVerifications.push('kyc_verification');
			}

			if (!lodash.isEmpty(user.phone_number)) {
				userVerifications.push('sms_verification');
			}

			if (user.email_verified) {
				userVerifications.push('email_verification');
			}

			if(!lodash.isEmpty(user.bank_account) && user.bank_account.some((account) => account.status === 3)){
				userVerifications.push('bank_verification');
			}

			loggerPlugin.debug(
				'AUTO TIER UPGRADE PLUGIN',
				'User verifications',
				userVerifications
			);

			for (const tier of checkedTiers) {
				if (lodash.isEmpty(tier.requirements)) {
					loggerPlugin.debug(
						'AUTO TIER UPGRADE PLUGIN',
						`Tier ${tier.id} does not have any requirement set`
					);
					break;
				}

				if (lodash.difference(tier.requirements, userVerifications).length === 0) {
					loggerPlugin.verbose(
						'AUTO TIER UPGRADE PLUGIN',
						`User ${user.email} meets requirements for tier`,
						tier.id
					);

					updatedLevel = tier.id;
				}
			}

			if (updatedLevel > userLevel) {
				loggerPlugin.verbose(
					'AUTO TIER UPGRADE PLUGIN',
					`User ${user.email} level will be changed from ${userLevel} to ${updatedLevel}`
				);

				await toolsLib.user.changeUserVerificationLevelById(user.id, updatedLevel);
			}
		}
	}
};

const cronjob = cron.schedule('0 0 0 * * *', async () => {
	loggerPlugin.verbose(
		'/plugins/automatic-tier-upgrade Upgrade start'
	);
	try {
		await runner();
	} catch (err) {
		loggerPlugin.error(
			'/plugins/automatic-tier-upgrade error during upgrade:',
			err.message
		);
	}
}, {
	scheduled: false
});

init()
	.then(() => {
		cronjob.start();
		
		app.get(
			'/plugins/automatic-tier-upgrade/available-requirements',
			[toolsLib.security.verifyBearerTokenExpressMiddleware(['admin'])],
			(req, res) => {
				loggerPlugin.verbose(
					req.uuid,
					'/plugins/automatic-tier-upgrade/available-requirements',
					req.auth.sub
				);

				return res.json(AVAILABLE_REQUIREMENTS);
			}
		);

		app.get(
			'/plugins/automatic-tier-upgrade/requirements',
			[toolsLib.security.verifyBearerTokenExpressMiddleware(['admin'])],
			async (req, res) => {
				loggerPlugin.verbose(
					req.uuid,
					'GET /plugins/automatic-tier-upgrade/requirements auth',
					req.auth.sub
				);

				try {
					const tiers = await findAllTiers();

					let response = {};

					for (const tier of tiers) {
						response[tier.id] = tier.requirements;
					}

					return res.json(response);
				} catch (err) {
					loggerPlugin.error(
						req.uuid,
						'GET /plugins/automatic-tier-upgrade/requirements err',
						err.message
					);
					return res.status(err.status || 400).json({message: err.message});
				}
			}
		);

		app.put(
			'/plugins/automatic-tier-upgrade/requirements',
			[
				toolsLib.security.verifyBearerTokenExpressMiddleware(['admin']),
				body('tier').isInt({ min: 1 }),
				body('requirements').isArray()
			],
			async (req, res) => {
				const errors = validationResult(req);
				if (!errors.isEmpty()) {
					return res.status(400).json({errors: errors.array()});
				}

				loggerPlugin.verbose(
					req.uuid,
					'PUT /plugins/automatic-tier-upgrade/requirements auth',
					req.auth.sub
				);

				try {
					const { tier: level, requirements } = req.body;

					const tier = await findTier(level);

					if (!tier) {
						throw new Error(`Tier ${level} does not exist`);
					}

					const formattedRequirements = lodash.uniq(requirements);

					if (lodash.difference(formattedRequirements, Object.keys(AVAILABLE_REQUIREMENTS)).length > 0) {
						throw new Error('Invalid requirements given');
					}

					await validateTierUpdate(level, requirements);

					const updatedTier = await updateTier(tier.id, requirements);

					return res.json(
						lodash.pick(updatedTier, ['id', 'requirements'])
					);
				} catch (err) {
					loggerPlugin.error(
						req.uuid,
						'PUT /plugins/automatic-tier-upgrade/requirements err',
						err.message
					);

					return res.status(err.status || 400).json({message: err.message});
				}
			});
	})
	.catch((err) => {
		loggerPlugin.error(
			'AUTOMATIC TIER UPGRADE PLUGIN error during initialization:',
			err.message
		);
	});
```

## Breakdown

We will go over the important parts of the script above that demonstrate how we create and use the newly created `requirements` column.

### Create and run the new migration in the init function

```javascript
const init = async () => {
	const umzugInstance = new umzug({
		storage: 'sequelize',
		storageOptions: {
			sequelize: toolsLib.database.getModel('sequelize'),
			modelName: 'PluginMigrations',
			tableName: 'PluginMigrations'
		},
		upName: 'up',
		downName: 'down',
		migrations: umzug.migrationsList(
			[
				{
					name: 'automatic_tier_upgrade-add_requirements_column',
					up: (queryInterface, Sequelize) => queryInterface.describeTable('Tiers')
						.then((table) => {
							if (table['requirements']) {
								return new Promise((resolve) => resolve());
							} else {
								return queryInterface.addColumn('Tiers', 'requirements', {
									type: Sequelize.JSONB,
									defaultValue: []
								});
							}
						}),
					down: (queryInterface, Sequelize) => queryInterface.describeTable('Tiers')
						.then((table) => {
							if (table['requirements']) {
								return queryInterface.removeColumn('Tiers', 'requirements');
							} else {
								return true;
							}
						})
				}
			],
			[toolsLib.database.getModel('sequelize').getQueryInterface(), sequelize]
		)
	});

	const pending = await umzugInstance.pending();
	if (pending.length > 0) {
		await umzugInstance.up('automatic_tier_upgrade-add_requirements_column');
	}
};
```

To add our new `requirements` column in the `Tier` table, we need to create a new migration for our database. We can do this using the `umzug` library. We are first creating an instance of `umzug` with the following configurations:

* `storage`: Should be set to `sequelize`
* `storageOptions`
  * `sequelize`: This is the sequelize instance being used throughout our exchagne. We can get this instance using the tools library `getModel` function
  * `modelName`: The name of the to be used model. Should be set to `PluginMigrations`&#x20;
  * `tableName`: The name of the table that stores migrations in the DB. Should be set to `PluginMigrations`
* `upName`: Should be set to `up`
* `downName`: Should be set to `down`
* `migrations`: This is where we use the umzug `migrationsList` function to create our migration. Each migration will need a `name`, `up` function, and `down` function.
  * `name`: The name of this migration. This should be formatted as `<PLUGIN_NAME>-<MIGRATION_ACTION>` e.g. `automatic_tier_upgrade-add_requirements_column`
  * `up`: The function to run when running the migration
  * `down`: The function to run when removing the migration

Once configured, we need to check if the migration has already been ran in the database. To do so, we can use `umzugInstance.pending()` to check if there are any migrations with the given name that have not been ran. If none are found, we run the migration using `umzugInstance.up(<MIGRATION_NAME>)`.

### Add helper functions for accessing the new column

```javascript
const findAllTiers = async () => {
	return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Tiers" ORDER BY id ASC', {
		raw: true,
		type: sequelize.QueryTypes.SELECT
	});
};

const findTier = async (id) => {
	return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Tiers" WHERE id = :id', {
		plain: true,
		raw: true,
		replacements: {
			id
		},
		type: sequelize.QueryTypes.SELECT
	});
};

const updateTier = async (id, requirements) => {
	const tier = await toolsLib.database.getModel('sequelize').query('UPDATE "Tiers" SET requirements = :requirements WHERE id = :id RETURNING *', {
		replacements: {
			id,
			requirements: JSON.stringify(requirements)
		}
	});

	return tier[0][0];
};
```

Newly created columns are not included in the base database model for the Kit. This means we can't access the column using basic Tools Library functions. Instead, we need to use the Sequelize instance for the exchange to run raw SQL queries. We can do this using the `getModel('sequelize').query(...)` function. For our plugin, we need to get tiers with the `requirements` column included and update the `requirements` column.

The rest of the script is using the newly created column to set requirements for a tier level. There is also a cron job that upgrades user levels if they meet the `requirements` set for a tier level.&#x20;


# Advanced Tutorial: Creating a new database table

In this tutorial, we will go over how to create a new table in our database by creating a new migration. To illustrate this, we are creating a plugin that allows users to add their twitter usernames. The requirements will be stored in a newly created table `Twitters` in the DB. This table will be created by a migration using the [`umzug`](https://www.npmjs.com/package/umzug) library.

{% hint style="danger" %}
We DO NOT recommend creating a new table in the DB. This can potentially have cause adverse side effects on your exchange.
{% endhint %}

## Script

```javascript
'use strict';

const {
	app,
	loggerPlugin,
	toolsLib
} = this.pluginLibraries;
const sequelize = require('sequelize');
const umzug = require('umzug');
const { body, validationResult } = require('express-validator');

const init = async () => {
	const umzugInstance = new umzug({
		storage: 'sequelize',
		storageOptions: {
			sequelize: toolsLib.database.getModel('sequelize'),
			modelName: 'PluginMigrations',
			tableName: 'PluginMigrations'
		},
		upName: 'up',
		downName: 'down',
		migrations: umzug.migrationsList(
			[
				{
					name: 'twitter_plugin-create_twitter_table',
					up: (queryInterface, Sequelize) => queryInterface.createTable(
						'Twitters',
						{
							id: {
								allowNull: false,
								autoIncrement: true,
								primaryKey: true,
								type: Sequelize.INTEGER
							},
							user_id: {
								type: Sequelize.INTEGER,
								onDelete: 'CASCADE',
								allowNull: false,
								references: {
									model: 'Users',
									key: 'id'
								}
							},
							username: {
								type: Sequelize.STRING,
								allowNull: false,
								unique: true
							},
							updated_at: {
								allowNull: false,
								type: Sequelize.DATE,
								defaultValue: Sequelize.literal('NOW()')
							},
							created_at: {
								allowNull: false,
								type: Sequelize.DATE,
								defaultValue: Sequelize.literal('NOW()')
							}
						},
						{
							timestamps: true,
							underscored: true
						}
					),
					down: (queryInterface, Sequelize) => queryInterface.dropTable('Twitters')
				}
			],
			[toolsLib.database.getModel('sequelize').getQueryInterface(), sequelize]
		)
	});

	const pending = await umzugInstance.pending();
	if (pending.length > 0) {
		await umzugInstance.up('twitter_plugin-create_twitter_table');
	}
};

const findUserTwitter = async (user_id) => {
	return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Twitters" WHERE user_id = :user_id', {
		plain: true,
		raw: true,
		replacements: {
			user_id
		},
		type: sequelize.QueryTypes.SELECT
	});
};

const createUserTwitter = async (user_id, username) => {
	const twitter = await toolsLib.database.getModel('sequelize').query('INSERT INTO "Twitters" (user_id, username) VALUES (:user_id, :username) RETURNING *', {
		replacements: {
			user_id,
			username
		},
		type: sequelize.QueryTypes.INSERT
	});

	return twitter[0][0];
};

const updateUserTwitter = async (user_id, username) => {
	const twitter = await toolsLib.database.getModel('sequelize').query('UPDATE "Twitters" SET username = :username WHERE user_id = :user_id RETURNING *', {
		replacements: {
			user_id,
			username
		},
		type: sequelize.QueryTypes.UPDATE
	});

	return twitter[0][0];
};

init()
	.then(() => {
		app.get(
			'/plugins/twitter/username',
			[toolsLib.security.verifyBearerTokenExpressMiddleware(['user'])],
			async (req, res) => {
				loggerPlugin.verbose(
					req.uuid,
					'GET /plugins/twitter/username auth',
					req.auth.sub
				);

				const { id } = req.auth.sub;

				try {
					const user = await toolsLib.user.getUserByKitId(id);

					if (!user) {
						throw new Error('User not found');
					}

					const twitter = await findUserTwitter(user.id);

					if (!twitter) {
						throw new Error('User twitter username not found');
					}

					return res.json(twitter);
				} catch (err) {
					loggerPlugin.error(
						req.uuid,
						'GET /plugins/twitter/username err',
						err.message
					);
					return res.status(err.status || 400).json({message: err.message});
				}
			}
		);

		app.post(
			'/plugins/twitter/username',
			[
				toolsLib.security.verifyBearerTokenExpressMiddleware(['user']),
				body('username').isString().notEmpty()
			],
			async (req, res) => {
				const errors = validationResult(req);
				if (!errors.isEmpty()) {
					return res.status(400).json({errors: errors.array()});
				}

				const { id } = req.auth.sub;
				const { username } = req.body;

				loggerPlugin.verbose(
					req.uuid,
					'POST /plugins/twitter/username username:',
					username
				);

				try {
					const user = await toolsLib.user.getUserByKitId(id);

					if (!user) {
						throw new Error('User not found');
					}

					const twitter = await findUserTwitter(user.id);

					if (twitter) {
						throw new Error('User already has twitter username set');
					}

					const result = await createUserTwitter(user.id, username);

					return res.json(result);
				} catch (err) {
					loggerPlugin.error(
						req.uuid,
						'POST /plugins/twitter/username err:',
						err.message
					);
					return res.status(err.status || 400).json({message: err.message});
				}
			}
		);

		app.put(
			'/plugins/twitter/username',
			[
				toolsLib.security.verifyBearerTokenExpressMiddleware(['user']),
				body('username').isString().notEmpty()
			],
			async (req, res) => {
				const errors = validationResult(req);
				if (!errors.isEmpty()) {
					return res.status(400).json({errors: errors.array()});
				}

				const { id } = req.auth.sub;
				const { username } = req.body;

				loggerPlugin.verbose(
					req.uuid,
					'PUT /plugins/twitter/username username:',
					username
				);

				try {
					const user = await toolsLib.user.getUserByKitId(id);

					if (!user) {
						throw new Error('User not found');
					}

					const twitter = await findUserTwitter(user.id);

					if (!twitter) {
						throw new Error('User twitter username not found');
					}

					if (twitter.username === username) {
						throw new Error(`User twitter username is already ${username}`);
					}

					const result = await updateUserTwitter(user.id, username);

					return res.json(result);
				} catch (err) {
					loggerPlugin.error(
						req.uuid,
						'PUT /plugins/twitter/username err:',
						err.message
					);
					return res.status(err.status || 400).json({message: err.message});
				}
			}
		);
	})
	.catch((err) => {
		loggerPlugin.error(
			'TWITTER PLUGIN error during initialization:',
			err.message
		);
	});
```

## Breakdown

For this tutorial, we will only go over how we create and use the newly created DB table `Twitters`.

### Create New Table Migration

```javascript
const init = async () => {
	const umzugInstance = new umzug({
		storage: 'sequelize',
		storageOptions: {
			sequelize: toolsLib.database.getModel('sequelize'),
			modelName: 'PluginMigrations',
			tableName: 'PluginMigrations'
		},
		upName: 'up',
		downName: 'down',
		migrations: umzug.migrationsList(
			[
				{
					name: 'twitter_plugin-create_twitter_table',
					up: (queryInterface, Sequelize) => queryInterface.createTable(
						'Twitters',
						{
							id: {
								allowNull: false,
								autoIncrement: true,
								primaryKey: true,
								type: Sequelize.INTEGER
							},
							user_id: {
								type: Sequelize.INTEGER,
								onDelete: 'CASCADE',
								allowNull: false,
								references: {
									model: 'Users',
									key: 'id'
								}
							},
							username: {
								type: Sequelize.STRING,
								allowNull: false,
								unique: true
							},
							updated_at: {
								allowNull: false,
								type: Sequelize.DATE,
								defaultValue: Sequelize.literal('NOW()')
							},
							created_at: {
								allowNull: false,
								type: Sequelize.DATE,
								defaultValue: Sequelize.literal('NOW()')
							}
						},
						{
							timestamps: true,
							underscored: true
						}
					),
					down: (queryInterface, Sequelize) => queryInterface.dropTable('Twitters')
				}
			],
			[toolsLib.database.getModel('sequelize').getQueryInterface(), sequelize]
		)
	});

	const pending = await umzugInstance.pending();
	if (pending.length > 0) {
		await umzugInstance.up('twitter_plugin-create_twitter_table');
	}
};
```

To create our new `Twitters` table, we need to create a new migration for our database. We can do this using the `umzug` library. We are first creating an instance of `umzug` with the following configurations:

* `storage`: Should be set to `sequelize`
* `storageOptions`
  * `sequelize`: This is the sequelize instance being used throughout our exchagne. We can get this instance using the tools library `getModel` function
  * `modelName`: The name of the to be used model. Should be set to `PluginMigrations`&#x20;
  * `tableName`: The name of the table that stores migrations in the DB. Should be set to `PluginMigrations`
* `upName`: Should be set to `up`
* `downName`: Should be set to `down`
* `migrations`: This is where we use the umzug `migrationsList` function to create our migration. Each migration will need a `name`, `up` function, and `down` function.
  * `name`: The name of this migration. This should be formatted as `<PLUGIN_NAME>-<MIGRATION_ACTION>` e.g. `twitter_plugin-create_twitter_table`
  * `up`: The function to run when running the migration. Should be formatted as a basic sequelize migration.
  * `down`: The function to run when removing the migration. Should be formatted as a basic sequelize migration.

Once configured, we need to check if the migration has already been ran in the database. To do so, we can use `umzugInstance.pending()` to check if there are any migrations with the given name that have not been ran. If none are found, we run the migration using `umzugInstance.up(<MIGRATION_NAME>)`.

### Add helper functions for accessing the new table

```javascript
const findUserTwitter = async (user_id) => {
	return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Twitters" WHERE user_id = :user_id', {
		plain: true,
		raw: true,
		replacements: {
			user_id
		},
		type: sequelize.QueryTypes.SELECT
	});
};

const createUserTwitter = async (user_id, username) => {
	const twitter = await toolsLib.database.getModel('sequelize').query('INSERT INTO "Twitters" (user_id, username) VALUES (:user_id, :username) RETURNING *', {
		replacements: {
			user_id,
			username
		},
		type: sequelize.QueryTypes.INSERT
	});

	return twitter[0][0];
};

const updateUserTwitter = async (user_id, username) => {
	const twitter = await toolsLib.database.getModel('sequelize').query('UPDATE "Twitters" SET username = :username WHERE user_id = :user_id RETURNING *', {
		replacements: {
			user_id,
			username
		},
		type: sequelize.QueryTypes.UPDATE
	});

	return twitter[0][0];
};
```

Newly created tables are not included in the base sequelize instance for the Kit. This means we can't access the column using basic Tools Library functions. Instead, we need to use the Sequelize instance for the exchange to run raw SQL queries. We can do this using the `getModel('sequelize').query(...)` function. For our plugin, we need to get, create, and update `Twitters` rows for new users.

The rest of the script is using the newly created table to get, set, and update user Twitter  usernames.




---

[Next Page](/llms-full.txt/1)

