Full Code of dilan-dio4/Keagate for AI

main 0581525abb6d cached
93 files
265.5 KB
69.2k tokens
133 symbols
1 requests
Download .txt
Showing preview only (290K chars total). Download the full file or copy to clipboard to get everything.
Repository: dilan-dio4/Keagate
Branch: main
Commit: 0581525abb6d
Files: 93
Total size: 265.5 KB

Directory structure:
gitextract_ifetv_mv/

├── .eslintrc.json
├── .gitignore
├── .nvmrc
├── LICENCE
├── README.md
├── TODO.md
├── assets/
│   └── coins.json
├── config/
│   └── default.json
├── package.json
├── packages/
│   ├── api-providers/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── GenericProvider.ts
│   │   │   ├── NowNodesProvider.ts
│   │   │   ├── QuickNodeProvider.ts
│   │   │   ├── SoChainProvider.ts
│   │   │   ├── TatumProvider.ts
│   │   │   ├── index.ts
│   │   │   └── units.ts
│   │   └── tsconfig.package.json
│   ├── backend/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── activityLoop.ts
│   │   │   ├── adminWallets/
│   │   │   │   ├── GenericAdminWallet.ts
│   │   │   │   ├── coinlib/
│   │   │   │   │   └── AdminCoinlibWrapper.ts
│   │   │   │   └── native/
│   │   │   │       ├── GenericNativeAdminWallet.ts
│   │   │   │       ├── Polygon/
│   │   │   │       │   └── index.ts
│   │   │   │       └── Solana/
│   │   │   │           ├── HdWallet.ts
│   │   │   │           └── index.ts
│   │   │   ├── config.ts
│   │   │   ├── context.ts
│   │   │   ├── currenciesToClients.ts
│   │   │   ├── devServer.ts
│   │   │   ├── index.ts
│   │   │   ├── limiters.ts
│   │   │   ├── logger.ts
│   │   │   ├── middlewares/
│   │   │   │   └── auth.ts
│   │   │   ├── mongo/
│   │   │   │   ├── generator.ts
│   │   │   │   └── index.ts
│   │   │   ├── routes/
│   │   │   │   ├── activePayments.ts
│   │   │   │   ├── createPayment.ts
│   │   │   │   ├── invoiceClient.ts
│   │   │   │   ├── invoiceStatus.ts
│   │   │   │   ├── paymentStatus.ts
│   │   │   │   ├── paymentsByExtraId.ts
│   │   │   │   ├── swagger.ts
│   │   │   │   └── types.ts
│   │   │   ├── transactionalWallets/
│   │   │   │   ├── GenericTransactionalWallet.ts
│   │   │   │   ├── coinlib/
│   │   │   │   │   ├── TransactionalCoinlibWrapper.ts
│   │   │   │   │   └── trxLimits.ts
│   │   │   │   └── native/
│   │   │   │       ├── GenericNativeTransactionalWallet.ts
│   │   │   │       ├── Polygon/
│   │   │   │       │   └── index.ts
│   │   │   │       └── Solana/
│   │   │   │           └── index.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   └── tsconfig.package.json
│   ├── common/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── config.ts
│   │   │   ├── currencies.ts
│   │   │   ├── fetch.ts
│   │   │   ├── index.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   └── tsconfig.package.json
│   ├── invoice-client/
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── src/
│   │   │   ├── components/
│   │   │   │   ├── App.tsx
│   │   │   │   ├── Invoice.tsx
│   │   │   │   └── ThreeDotsOverlay.tsx
│   │   │   ├── index.tsx
│   │   │   ├── styles/
│   │   │   │   └── globals.css
│   │   │   ├── utils/
│   │   │   │   ├── useDeviceSize.tsx
│   │   │   │   └── utils.ts
│   │   │   └── vite-env.d.ts
│   │   ├── tailwind.config.js
│   │   ├── tsconfig.json
│   │   ├── tsconfig.package.json
│   │   └── vite.config.ts
│   └── scripts/
│       ├── assets/
│       │   └── default.conf
│       ├── keagate.sh
│       ├── package.json
│       ├── src/
│       │   ├── DelegateLogger.ts
│       │   ├── configure.ts
│       │   ├── index.ts
│       │   ├── opts.ts
│       │   ├── setupMongo.ts
│       │   ├── setupNginx.ts
│       │   ├── setupSeeds.ts
│       │   └── setupWallets.ts
│       └── tsconfig.package.json
├── pnpm-workspace.yaml
├── prettier.config.js
├── tsconfig.base.json
├── tsconfig.json
└── tsconfig.project.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.json
================================================
{
    "root": true,
    "parser": "@typescript-eslint/parser",
    "plugins": [
      "@typescript-eslint"
    ],
    "extends": [
      "eslint:recommended",
      "plugin:@typescript-eslint/eslint-recommended",
      "plugin:@typescript-eslint/recommended"
    ],
    "env": {
        "commonjs": true,
        "es2020": true,
        "node": true
    },
    "rules": {
      "@typescript-eslint/explicit-module-boundary-types": "off"
    }
  }

================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

# typescript
*.tsbuildinfo

# Local Netlify folder
.netlify

**/package-lock.json
**/pnpm-lock.yaml
**/node_modules
**/.env
**/local.settings.json

**/tsconfig.package.tsbuildinfo
**/build/
**/dist/

!config
config/*
!config/default.json

================================================
FILE: .nvmrc
================================================
16

================================================
FILE: LICENCE
================================================
Elastic License 2.0 (ELv2)

URL: https://www.elastic.co/licensing/elastic-license

## Acceptance

By using the software, you agree to all of the terms and conditions below.

## Copyright License

The licensor grants you a non-exclusive, royalty-free, worldwide,
non-sublicensable, non-transferable license to use, copy, distribute, make
available, and prepare derivative works of the software, in each case subject to
the limitations and conditions below.

## Limitations

You may not provide the software to third parties as a hosted or managed
service, where the service provides users with access to any substantial set of
the features or functionality of the software.

You may not move, change, disable, or circumvent the license key functionality
in the software, and you may not remove or obscure any functionality in the
software that is protected by the license key.

You may not alter, remove, or obscure any licensing, copyright, or other notices
of the licensor in the software. Any use of the licensor’s trademarks is subject
to applicable law.

## Patents

The licensor grants you a license, under any patent claims the licensor can
license, or becomes able to license, to make, have made, use, sell, offer for
sale, import and have imported the software, in each case subject to the
limitations and conditions in this license. This license does not cover any
patent claims that you cause to be infringed by modifications or additions to
the software. If you or your company make any written claim that the software
infringes or contributes to infringement of any patent, your patent license for
the software granted under these terms ends immediately. If your company makes
such a claim, your patent license ends immediately for work on behalf of your
company.

## Notices

You must ensure that anyone who gets a copy of any part of the software from you
also gets a copy of these terms.

If you modify the software, you must include in any modified copies of the
software prominent notices stating that you have modified the software.

## No Other Rights

These terms do not imply any licenses other than those expressly granted in
these terms.

## Termination

If you use the software in violation of these terms, such use is not licensed,
and your licenses will automatically terminate. If the licensor provides you
with a notice of your violation, and you cease all violation of this license no
later than 30 days after you receive that notice, your licenses will be
reinstated retroactively. However, if you violate these terms after such
reinstatement, any additional violation of these terms will cause your licenses
to terminate automatically and permanently.

## No Liability

*As far as the law allows, the software comes as is, without any warranty or
condition, and the licensor will not be liable to you for any damages arising
out of these terms or the use or nature of the software, under any kind of
legal claim.*

## Definitions

The **licensor** is the entity offering these terms, and the **software** is the
software the licensor makes available under these terms, including any portion
of it.

**you** refers to the individual or entity agreeing to these terms.

**your company** is any legal entity, sole proprietorship, or other kind of
organization that you work for, plus all organizations that have control over,
are under the control of, or are under common control with that
organization. **control** means ownership of substantially all the assets of an
entity, or the power to direct its management and policies by vote, contract, or
otherwise. Control can be direct or indirect.

**your licenses** are all the licenses granted to you for the software under
these terms.

**use** means anything you do with the software requiring one of your licenses.

**trademark** means trademarks, service marks, and similar rights.

================================================
FILE: README.md
================================================
<!-- markdownlint-disable MD033 MD041 -->

<br />

<!-- http://ipa-reader.xyz/?text=ki%3Age%C9%AAt&voice=Joey --->

<h2 align="center">

Keagate *(&#107;&#105;&colon;&#103;&#101;&#618;&#116;)* – A High-Performance Cryptocurrency Payment Gateway

</h2>
<!--
<h4 align="center">
  <b>🚧 This project is actively in development 🚧</b>
</h4>
--->

<h4 align="center">
    <img alt="Snyk vulnerabilities" src="https://shields.io/snyk/vulnerabilities/github/dilan-dio4/keagate?style=flat-square" />
    <img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/dilan-dio4/Keagate?style=flat-square">
    <img alt="GitHub top language" src="https://img.shields.io/github/languages/top/dilan-dio4/Keagate?style=flat-square">
    <a href="https://gitter.im/Keagate/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link"><img alt="Gitter" src="https://img.shields.io/gitter/room/dilan-dio4/Keagate?style=flat-square"></a>
    <a href="https://dilan-dio4.github.io/keagate-example-swagger/"><img alt="Swagger Validator" src="https://img.shields.io/swagger/valid/3.0?specUrl=https%3A%2F%2Fraw.githubusercontent.com%2Fdilan-dio4%2Fkeagate-example-swagger%2Fmain%2Fkeagate-openapi3.json&style=flat-square"></a>
</h4>

<br />

<p align="center">
  <img src="assets/icon-tiny.png" width="150" alt="Keagate Icon">
</p>

<!-- TODO: Keagate Vector --->

<!-- TABLE OF CONTENTS -->
## Table of Contents

* [About the Project](#about-the-project)
  * [Purpose](#purpose)
* [Installation](#installation)
  * [One-liner](#one-liner)
  * [Manual](#manual-installation)
* [Configuration](#configuration)
  * [CLI](#cli)
  * [Custom](#custom)
* [Payment Lifecycle](#payment-lifecycle)
  * [API Method](#api-method)
  * [Invoice Client Method](#invoice-client-method)
* [Instant Payment Notifications](#instant-payment-notifications)
* [Development](#development)
  * [Adding an API Route](#adding-an-api-route)
  * [Customizing the Invoice Interface](#customizing-the-invoice-interface)

## About the Project

Keagate is a self-hosted, high-performance cryptocurrency payment gateway. Payments can be administered [via API](https://dilan-dio4.github.io/keagate-example-swagger/) for flexibility or with the built-in invoicing client (*image below*).

**Supported currencies: Bitcoin, Ethereum, Dogecoin, Solana, Litecoin, Polygon, Dash,** *Ripple (coming soon), Tron (coming soon)*.

<p align="left">
  <img src="assets/invoice-frame.png" width="600" alt="Invoice Preview">
</p>

### Purpose

* No KYC
* No fees, middleman, or escrow
* Self-hosted/Private
* Easily extensible
* Lightweight and highly performant

Funds go directly to your wallet via a one-time address that is generated for each payment.

## Installation

<p align="left">
  <a href="https://www.youtube.com/watch?v=dxMZIbeRJac">
    <img src="assets/yt-screenshot.png" width="700" alt="Keagate Youtube Preview">
  </a>
</p>

### One-liner

The purpose of this installation script is to get Keagate up-and-running quickly in a Linux environment. The CLI will guide you in configuring, managing, and securing the instance.

```bash
bash -c "$(curl -sSL https://raw.githubusercontent.com/dilan-dio4/Keagate/main/packages/scripts/keagate.sh)"
```

*Alternate*:

```bash
curl -o keagate.sh https://raw.githubusercontent.com/dilan-dio4/Keagate/main/packages/scripts/keagate.sh
chmod +x keagate.sh
./keagate.sh
```

This helper script has been tested on...

* Ubuntu 18+
* Debian 10+
* Amazon Linux 4.14+
* CentOS 7.9+

...via AWS and Azure.

<!-- No Docker quick install on Redhat RHEL -->

This script should run successfully on most flavors of Linux with some configuration. Otherwise, use the manual build, as it's fairly straightforward.

### Manual Installation

#### Prerequisites

* MongoDB – [Install](https://www.mongodb.com/docs/manual/installation/)
  * Running on your machine **OR** remotely via a connection string
* Web server (like Nginx or Apache2) – [Install](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
  * Serving as a reverse proxy to `localhost:8081`
  * `8081` is the default port that Keagate runs on, can be changed via the [*PORT* configuration option](#custom).
* Node > 14 and NPM – [Install](https://github.com/nvm-sh/nvm#installing-and-updating)
  * Use of `nvm` to manage Node and NPM is recommended

```bash
# +++ Don't have Node?
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm install 16
nvm use 16
# ---

npm i -g pnpm
pnpm setup
pnpm -g pm2

git clone https://github.com/dilan-dio4/Keagate
cd Keagate
pnpm i
pnpm run build

# +++ Configure Keagate with:
node packages/scripts/build/configure.js
# --- OR manually (see Configuration section)

pm2 start packages/backend/build/index.js --name Keagate --time
```

## Configuration

Keagate requires some configuration. This is done via a file called `local.json` in `/config`, next to `default.json`. This file will automatically be used when you start Keagate. *Note that parameters in `local.json` will overwrite those in `default.json`*.

There are **two** methods to configure Keagate, and they can be used in conjunction with each other.

### CLI

Keagate has a built-in CLI to build configurations in [packages/scripts](packages/scripts/src/configure.ts). After you've cloned and built the package. Head to the root *Keagate* directory and execute the following:

```bash
node packages/scripts/build/configure.js
```

*Note – this CLI is automatically launched in the one-liner installation script.*

The CLI will write the `config/local.json` file upon completion unless one already exists. In that case, it will write to `config/local2.json` and ask that you manually merge your new parameters, as needed.

### Custom

Create or open the file `local.json` in `/config`. You can use the provided `default.json` file as a reference **(your `local.json` will override these)**.

*The schema of the Keagate configuration can be seen (in TypeScript) at [packages/common/src/config.ts](packages/common/src/config.ts).*

#### Currencies

To configure a single currency, add an object with the key of the currency's ticker with the following attributes:

Ticker can be one of `'LTC', 'BTC', 'ETH', 'DOGE', 'SOL', 'DASH', or 'MATIC'`. [See example](#example).

| Key                              | Description                    | Required | Default |
|----------------------------------|----------------------------|----------------------------------|--|
| `ADMIN_PUBLIC_KEY`          | Public key (address) of your admin wallet    | **Yes** | *null* (string) |
| `ADMIN_PRIVATE_KEY`         | Private key of admin wallet. Only needed if you plan on programmatically sending transactions  | No | *null* (string) |

#### Protected options

This section details specific configuration parameters that should be handled with extra care. A malicious actor could manipulate the integrity of payments if they had access to these parameters.

**There's a built-in script to securely generate and print these values at random:**

```bash
node packages/scripts/build/setupSeeds.js
# OR
ts-node packages/scripts/src/setupSeeds.ts

# Prints
{
  "INVOICE_ENC_KEY": "5036...9cc3",
  "SEED": "eb08...3afc",
  "KEAGATE_API_KEY": "9fac8f7d...c6568f97",
  "IPN_HMAC_SECRET": "e50dd645...ea5baf54"
}
```

| Key                              | Description                    | Required | Default |
|----------------------------------|----------------------------|----------------------------------|--|
| `SEED`         | Seed for transactional wallet generator. Must be a 128-bit hex string. **Protect this value in production** | **Yes** | *null* (string) |
| `KEAGATE_API_KEY`         | Api key that will be required in administrative request's `keagate-api-key` header. **Protect this value in production** | No | 'API-KEY' (string) |
| `INVOICE_ENC_KEY`         | Key that will be used to encrypt payment IDs when distributed via invoice. **Protect this value in production** | **Yes** | *null* (string) |
| `IPN_HMAC_SECRET`         | Key of the HMAC signature that is set in the `x-keagate-sig` header of each POST request when using [Instant Payment Notifications](#instant-payment-notifications). **Protect this value in production** | No | *null* (string) |

#### Other options

| Key                              | Description                    | Required | Default |
|----------------------------------|----------------------------|----------------------------------|--|
| `IP_WHITELIST`         | List of IP address ["1.1.1.1" , "2.2.2.2",...] to be whitelisted for administrative requests | No | [] (string[]) |
| `TRANSACTION_TIMEOUT` | Milliseconds by which payments will be valid for. After that, the payment is expired | No | 1200000 [20 Minutes] (number) |
| `TRANSACTION_MIN_REFRESH_TIME` | Minimum milliseconds by which transactions will idle between refreshes | No | 30000 [30 Seconds] (number) |
| `TRANSACTION_SLIPPAGE_TOLERANCE` | Percentage of a total payment that is discounted as slippage.<br /><br />Example: a TRANSACTION_SLIPPAGE_TOLERANCE of 0.02 for a 100 SOL payment will be fulfilled at 98 SOL. | No | 0.02 (number) |
| `BLOCKBOOK_RETRY_DELAY` | Milliseconds to wait before re-trying a failed Blockbook request. | No | 5000 (number) |
| `MONGO_CONNECTION_STRING` | Connection string for MongoDB instance including any authentication. | No | 'mongodb://localhost:27017' (string) |
| `MONGO_KEAGATE_DB` | Mongo database to use for storing/managing payments | No | 'keagate' (string) |
| `IS_DEV` | **For development only**. Turn on testnets for given currencies and activate development features | No | false (boolean) |
| `HOST` | Your domain or IP that Keagate is running on. **This is used for aesthetics and has no functional effect on Keagate** | No | *null* (string) |
| `PORT` | The port that Keagate's backend API will run on | No | 8081 (number) |

<!-- | `USE_SO_CHAIN` | [SoChain](https://sochain.com/api/#introduction) is a free blockchain infrastructure API for that allows for 300 requests/minute free-of-charge.<br /><br />Setting this to `true` will utilize SoChain for part of the btc, dash, and ltc payment process. **Recommended** | No | true (boolean) | -->

#### Example

Your `config/local.json` could look something like:

```js
{
  "LTC": {
    "ADMIN_PUBLIC_KEY": "MY_WALLET_ADDRESS",
    "ADMIN_PRIVATE_KEY": "MY_PRIVATE_KEY"
  },

  "KEAGATE_API_KEY": "abcd123",
  "IP_WHITELIST": ["1.1.1.1","2.2.2.2"]
  // ...
}
```

## Payment Lifecycle

### API Method

The workflow for creating & confirming payments in the API-driven method is as follows:

1. Invoke the [`createPayment`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/post_createPayment) route. Pass the following parameters in the JSON body:

    * *amount* – The total value of the payment
    * *currency* – Shorthand name of the desired currency (e.g. `"LTC"`)
    * [*ipnCallbackUrl* – URL to synchronously send payment status updates to](#instant-payment-notifications)
    * **Optional** *extraId* – Some external identification string (useful for manually managing the identity of payment with [`getPaymentsByExtraId`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/get_getPaymentsByExtraId))

2. Notify your customer to send the *amount* to the *publicKey* returned from [`createPayment`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/post_createPayment).
  
    * **Optional** Display [other metadata](https://dilan-dio4.github.io/keagate-example-swagger/#model-def-0) as well, for a streamlined user experience. Use the screenshot above as a reference.

3. Wait for your customer to send the payment.

    * **Optional** Invoke the [`getInvoiceStatus`](https://dilan-dio4.github.io/keagate-example-swagger/#/Invoice/get_getInvoiceStatus) route on a timer from the customer's device to provide real-time updates. *Note: this route doesn't return any sensitive information*.

4. Confirm and process the payment in your [IPN](#instant-payment-notifications) route after receiving a **CONFIRMED** status of a payment of the same *id* or *extraId*.

### Invoice Client Method

The workflow for creating & confirming payments with the built-in invoice client is as follows:

1. Invoke the [`createPayment`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/post_createPayment) route. Pass the following parameters in the JSON body:

    * *amount* – The total value of the payment
    * *currency* – Shorthand name of the desired currency (e.g. `"LTC"`)
    * Either or both of:
      * [*invoiceCallbackUrl* – Route that your customers will be directed to after their payment finishes](#invoice-client-callback-url)
      * [*ipnCallbackUrl* – URL to synchronously send payment status updates to](#instant-payment-notifications)
    * **Optional** *extraId* – Some external identification string (useful for manually managing the identity of payment with [`getPaymentsByExtraId`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/get_getPaymentsByExtraId))

2. Direct users to the URL route provided in the `invoiceUrl` attribute returned from [`createPayment`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/post_createPayment).

    * *Note: this is not a full URL, just a path. It should be appended to the URL of your Keagate server*.

3. Confirm and process the payment with either method:

    * If you set *ipnCallbackUrl*, with your [IPN](#instant-payment-notifications) route after receiving a **CONFIRMED** status of a payment of the same *id* or *extraId*.
    * If you set *invoiceCallbackUrl*, with the [Invoice Client Callback Url](#invoice-client-callback-url) page that customers are directed to after the invoice client observes a finished payment.

### Invoice Client Callback Url

Two query parameters are appended to this URL when customers are directed to it from the invoice client:

* *invoice_id* – The encrypted id of the payment (as seen in the `invoiceUrl` attribute of [`createPayment`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/post_createPayment)).
* *status* – The [*assumed*] status of the payment: "CONFIRMED", "FAILED", "EXPIRED", etc. **This is just to help you visually notify the customer in your site, but cannot be trusted (see below)**.

**You must still confirm the payment server-side**. This is because a malicious customer could just manually go to this route and set the status to **CONFIRMED**. On your backend, use [`getPaymentStatus`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/get_getPaymentStatus) to actually validate the status.

*Note: you can send either a true database id or invoice_id [which are just encrypted database ids] to [`getPaymentStatus`](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/get_getPaymentStatus). Keagate can figure out which id it is based on the input length.*

Once you've validated the payment status via a server-side request from the *invoiceCallbackUrl* page, you can confirm and process the payment as normal.

## Instant Payment Notifications

To be notified of payment updates in real-time, use instant payment notifications (IPN).

### Use IPNs

1. Make sure you have configured the *IPN_HMAC_SECRET* attribute in [Configuration](#protected-options). This will allow you to guarantee the origin and trust in the integrity of incoming messages.
2. Have access to some API or serverless function that can be invoked publicly via URL.
3. Pass this URL into the `ipnCallbackUrl` attribute of your [*createPayment*](https://dilan-dio4.github.io/keagate-example-swagger/#/Payment/post_createPayment) requests.

Just like that, IPNs are all set up on Keagate. A POST request will be sent to the `ipnCallbackUrl` with a JSON object like that of [*TypeForRequest*](https://dilan-dio4.github.io/keagate-example-swagger/#model-def-0).

Before using these notifications, the last thing to do is validate all incoming messages via HMAC.

### Validate IPN Messages

The previously configured *IPN_HMAC_SECRET* is used as a key in the sha-512 HMAC signature generated for the `x-keagate-sig` header of each notification.

*Note: be sure to sort the request body alphabetically before generating your HMAC.*

Here's a NodeJS example of validating this header in Express.

```js
var crypto = require('crypto')
var express = require('express')

const app = express()
app.use(express.json())

app.post('/ipnCallback', (req, res) => {
  // +++ Generate my signature
  const hmac = crypto.createHmac('sha512', IPN_HMAC_SECRET)
  hmac.update(JSON.stringify(req.body, Object.keys(req.body).sort()))
  const signature = hmac.digest('hex')
  // ---

  if (signature === req.headers['x-keagate-sig']) {
    // Good to go!
    const id = req.body.id
    // ...
  } else {
    // This notification may be spoofed...
  }
});
```

## Development

Development experience and extensibility are the utmost priority of this package.

To get started:

1. Clone this repo.
2. Install `pnpm` globally with `npm i -g pnpm`
3. `cd Keagate && pnpm i`
4. Add a MongoDB connection to the `MONGO_CONNECTION_STRING` attribute in `config/local.json`, along with some admin wallet credentials and the other [required configuration parameters](#custom). For development, the [Mongo Atlas free tier](https://www.mongodb.com/cloud/atlas/signup) works great.
5. `pnpm run dev` to start the invoice client and backend.
    * Any changes in `packages/invoice-client/src` will be automatically reflected on refresh.
    * Any changes to the source of `packages/backend/src` will be reflected automatically via `ts-node-dev`.
    * Any changes to `config/local.json` have to be manually refreshed.

The backend will run at `127.0.0.1:8081`. You can see your Swagger API docs at `http://127.0.0.1/docs`. Also, a test IPN callback server will run at `127.0.0.1:8082/ipnCallback` and a test invoice client redirect static site will be available at `http://127.0.0.1/dev-callback-site`.

<details>

<summary>

### Adding an API Route

</summary>

Keagate follows the [Fastify plugin pattern](https://www.fastify.io/docs/latest/Reference/Plugins/). Place your route in [`packages/backend/src/routes`](packages/backend/src/routes). The `default export` of the file should be a function that takes a *Fastify* instance as a parameter. In that function, add your route to the provided *Fastify* instance. **Be sure to add a schema to your route via the `RouteShorthandOptions` type exported from Fastify. Schemas should be built with [TypeBox](https://github.com/sinclairzx81/typebox).

The schemas will appear in your Swagger docs for a unified developer experience.

Finally, in [`packages/backend/src/index.ts`](packages/backend/src/index.ts), register your new route like so:

```ts
import createPaymentStatusRoute from './routes/paymentStatus';
import createPaymentsByExtraIdRoute from './routes/paymentsByExtraId';
import create_YOUR_FUNCTIONALITY_Route from './routes/YOUR_FUNCTIONALITY'; // <--

// ...

server.register(createPaymentStatusRoute);
server.register(createPaymentsByExtraIdRoute);
server.register(create_YOUR_FUNCTIONALITY_Route); // <--
```

Use [`packages/backend/src/routes/activePayments.ts`](packages/backend/src/routes/activePayments.ts) as a reference of an authenticated route.

Use [`packages/backend/src/routes/invoiceStatus.ts`](packages/backend/src/routes/invoiceStatus.ts) as a reference of an unauthenticated route.

</details>
<details>

<summary>

### Customizing the Invoice Interface

</summary>

The invoice client is a statically built React package (via Vite). This static build is served in `backend`. This functionality can be seen [here](packages/backend/src/routes/invoiceClient.ts).

Editing the react package will automatically build to `packages/invoice-client/dist`, so just refresh the page to see any changes.

The source of `invoice-client`'s React project is pretty straightforward, so those familiar with React (& TailwindCSS) should have an easy time making their desired alterations.

</details>

<!--
<details>

<summary>

### Adding a currency

</summary>

There's four steps in adding a currency to this package.

1. Add the ticker, along with some metadata, to the currencies type in [packages/common/src/currencies.ts](packages/common/src/currencies.ts).
2. Create the admin wallet. This is where payments are finally sent to and presumably the real wallet of the client. Note that the admin wallet can also be used to programmatically send transactions as well.
    * Start by taking a look at [Solana's admin wallet](packages/backend/src/adminWallets/Solana/index.ts) and note that you only need to implement two functions: `getBalance` and `sendTransaction`. The class that admin wallets inherit from, [GenericAdminWallet](packages/backend/src/adminWallets/GenericAdminWallet.ts), handles class inheritance.
3. Create the transactional wallet. This class can be thought of a payment, since a new transactional wallet is created for every payment, along with a new Public Key and Private Key. Transactional wallets, and their associated payment data, are stored in Mongo.
    1. Start by taking a look at [Solana's transactional wallet](packages/backend/src/transactionalWallets/Solana/index.ts) and note that you only need to implement three functions: `fromNew`, `getBalance` and `_cashOut`. The class that transactional wallets inherit from, [GenericTransactionalWallet](packages/backend/src/transactionalWallets/GenericTransactionalWallet.ts), handles the rest.  
4. Add both the transactional and admin wallet classes to [packages/backend/src/currenciesToWallets.ts](packages/backend/src/currenciesToWallets.ts) so it can be referred to by ticker across the project

**And that's it!** Start the dev environment (`pnpm run dev`) and create a new payment of any amount with your new currency.

</details>

<details>

<summary>

### Adding a blockchain API provider

</summary>

The invoice client is a statically built React package (via Vite). This static build is served in `backend`. This functionality can be seen [here](packages/backend/src/routes/invoiceClient.ts).

Editing the react package will automatically build to `dist`, so just refresh the page to see the changes.

The source code in invoice client is pretty straight-forward, so anyone familiar with React (& TailwindCSS) should have an easy time making their desired alterations.

</details>

<details>

<summary>

### API Providers

</summary>

In order to check wallet balances and broadcast transactions, Keagate needs to interact with particular blockchain APIs. There's a variety of providers out there that support different sets of blockchains. This packages bundles up connectors to popular providers with a simple, unified API.

Existing connectors can be seen in the [packages/api-providers](packages/api-providers/src/) folder. All of the one available in this package provide generous free tiers. Simply pass your API keys with the configuration below.

Currently available API providers:

| Name  | Available chains |
|-----------------|--------------|
| NowNodes | dash, ltc, btc |
| Tatum | ltc, btc, ada, and xrp |

It's very easy to add a provider, see [TatumProvider.ts](packages/api-providers/src/TatumProvider.ts) as an example.

Make sure that one of the available API providers cover each currency you plan on using.

</details>

-->


================================================
FILE: TODO.md
================================================
# TODOs

* ~~Flexible expire timer UI in invoice client~~
  * ~~Formats like *DD-MM-YY hh:mm a* or *day-of-week hh:mm a* depending on expiration value.~~
* Inline web payments via JS SDK
  * Small pop-up UI (like Stripe)
* Native api for request throttle values
* Dynamic throttling
  * Keagate public API for base times
    * Blockbook and other providers have changing rate-limits
  * Based on number of active payments(?)
* Invoice client unconfirmed balance view
  * Only needed for certain chains (DASH, LTC, BTC, DOGE)


================================================
FILE: assets/coins.json
================================================
{
    "bitcoin": [
        {
            "address_type": 0,
            "address_type_p2sh": 5,
            "bech32_prefix": "bc",
            "bip115": false,
            "blockbook": [
                "https://btc1.trezor.io",
                "https://btc2.trezor.io",
                "https://btc3.trezor.io",
                "https://btc4.trezor.io",
                "https://btc5.trezor.io"
            ],
            "blocktime_seconds": 600,
            "cashaddr_prefix": null,
            "coin_label": "Bitcoin",
            "coin_name": "Bitcoin",
            "coin_shortcut": "BTC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
            "max_address_length": 34,
            "maxfee_kb": 2000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Bitcoin",
            "segwit": true,
            "shortcut": "BTC",
            "signed_message_header": "Bitcoin Signed Message:\n",
            "slip44": 0,
            "support": {
                "connect": true,
                "trezor1": "1.5.2",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 111,
            "address_type_p2sh": 196,
            "bech32_prefix": "tb",
            "bip115": false,
            "blockbook": [
                "https://tbtc1.trezor.io",
                "https://tbtc2.trezor.io"
            ],
            "blocktime_seconds": 600,
            "cashaddr_prefix": null,
            "coin_label": "Testnet",
            "coin_name": "Testnet",
            "coin_shortcut": "TEST",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
            "max_address_length": 34,
            "maxfee_kb": 10000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Testnet",
            "segwit": true,
            "shortcut": "TEST",
            "signed_message_header": "Bitcoin Signed Message:\n",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.5.2",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 70615956,
            "xpub_magic": 70617039,
            "xpub_magic_segwit_native": 73342198,
            "xpub_magic_segwit_p2sh": 71979618
        },
        {
            "address_type": 53,
            "address_type_p2sh": 55,
            "bech32_prefix": "acm",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Actinium",
            "coin_name": "Actinium",
            "coin_shortcut": "ACM",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "28d77872e23714562f49a1be792c276623c1bbe3fdcf21b6035cfde78b00b824",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 100000,
            "name": "Actinium",
            "segwit": true,
            "shortcut": "ACM",
            "signed_message_header": "Actinium Signed Message:\n",
            "slip44": 228,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.10",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 55,
            "address_type_p2sh": 16,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Axe",
            "coin_name": "Axe",
            "coin_shortcut": "AXE",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000c33631ca6f2f61368991ce2dc03306b5bb50bf7cede5cfbba6db38e52e6",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Axe",
            "segwit": false,
            "shortcut": "AXE",
            "signed_message_header": "DarkCoin Signed Message:\n",
            "slip44": 4242,
            "support": {
                "connect": true,
                "trezor1": "1.7.3",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 50221816,
            "xpub_magic": 50221772,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 230,
            "address_type_p2sh": 235,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "BitCash",
            "coin_name": "BitCash",
            "coin_shortcut": "BITC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "7d57d87ff3c15a521530af60edee1887fba9c193eb518face926785c4cd8f4f1",
            "max_address_length": 53,
            "maxfee_kb": 30000000,
            "min_address_length": 52,
            "minfee_kb": 1000,
            "name": "BitCash",
            "segwit": false,
            "shortcut": "BITC",
            "signed_message_header": "Bitcash Signed Message:\n",
            "slip44": 230,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.10",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 25,
            "address_type_p2sh": 5,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 300,
            "cashaddr_prefix": null,
            "coin_label": "Bitcloud",
            "coin_name": "Bitcloud",
            "coin_shortcut": "BTDX",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "000002d56463941c20eae5cb474cc805b646515d18bc7dc222a0885b206eadb0",
            "max_address_length": 34,
            "maxfee_kb": 1000000,
            "min_address_length": 27,
            "minfee_kb": 10000,
            "name": "Bitcloud",
            "segwit": false,
            "shortcut": "BTDX",
            "signed_message_header": "Diamond Signed Message:\n",
            "slip44": 218,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.10",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 0,
            "address_type_p2sh": 5,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://bch1.trezor.io",
                "https://bch2.trezor.io",
                "https://bch3.trezor.io",
                "https://bch4.trezor.io",
                "https://bch5.trezor.io"
            ],
            "blocktime_seconds": 600,
            "cashaddr_prefix": "bitcoincash",
            "coin_label": "Bitcoin Cash",
            "coin_name": "Bcash",
            "coin_shortcut": "BCH",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 546,
            "force_bip143": true,
            "fork_id": 0,
            "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
            "max_address_length": 34,
            "maxfee_kb": 500000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Bitcoin Cash",
            "segwit": false,
            "shortcut": "BCH",
            "signed_message_header": "Bitcoin Signed Message:\n",
            "slip44": 145,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 111,
            "address_type_p2sh": 196,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 600,
            "cashaddr_prefix": "bchtest",
            "coin_label": "Bitcoin Cash Testnet",
            "coin_name": "Bcash Testnet",
            "coin_shortcut": "TBCH",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": true,
            "fork_id": 0,
            "hash_genesis_block": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
            "max_address_length": 34,
            "maxfee_kb": 10000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Bitcoin Cash Testnet",
            "segwit": false,
            "shortcut": "TBCH",
            "signed_message_header": "Bitcoin Signed Message:\n",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "xprv_magic": 70615956,
            "xpub_magic": 70617039,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 38,
            "address_type_p2sh": 23,
            "bech32_prefix": "btg",
            "bip115": false,
            "blockbook": [
                "https://btg1.trezor.io",
                "https://btg2.trezor.io",
                "https://btg3.trezor.io",
                "https://btg4.trezor.io",
                "https://btg5.trezor.io"
            ],
            "blocktime_seconds": 600,
            "cashaddr_prefix": null,
            "coin_label": "Bitcoin Gold",
            "coin_name": "Bgold",
            "coin_shortcut": "BTG",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 546,
            "force_bip143": true,
            "fork_id": 79,
            "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
            "max_address_length": 34,
            "maxfee_kb": 500000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Bitcoin Gold",
            "segwit": true,
            "shortcut": "BTG",
            "signed_message_header": "Bitcoin Gold Signed Message:\n",
            "slip44": 156,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 111,
            "address_type_p2sh": 196,
            "bech32_prefix": "tbtg",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 600,
            "cashaddr_prefix": null,
            "coin_label": "Bitcoin Gold Testnet",
            "coin_name": "Bgold Testnet",
            "coin_shortcut": "TBTG",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 546,
            "force_bip143": true,
            "fork_id": 79,
            "hash_genesis_block": "00000000e0781ebe24b91eedc293adfea2f557b53ec379e78959de3853e6f9f6",
            "max_address_length": 34,
            "maxfee_kb": 500000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Bitcoin Gold Testnet",
            "segwit": true,
            "shortcut": "TBTG",
            "signed_message_header": "Bitcoin Gold Signed Message:\n",
            "slip44": 156,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 70615956,
            "xpub_magic": 70617039,
            "xpub_magic_segwit_native": 73342198,
            "xpub_magic_segwit_p2sh": 71979618
        },
        {
            "address_type": 4901,
            "address_type_p2sh": 5039,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Bitcoin Private",
            "coin_name": "Bprivate",
            "coin_shortcut": "BTCP",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": 42,
            "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602",
            "max_address_length": 95,
            "maxfee_kb": 1000000,
            "min_address_length": 35,
            "minfee_kb": 1000,
            "name": "Bitcoin Private",
            "segwit": false,
            "shortcut": "BTCP",
            "signed_message_header": "BitcoinPrivate Signed Message:\n",
            "slip44": 183,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 3,
            "address_type_p2sh": 125,
            "bech32_prefix": "btx",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Bitcore",
            "coin_name": "Bitcore",
            "coin_shortcut": "BTX",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Low": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "604148281e5c4b7f2487e5d03cd60d8e6f69411d613f6448034508cea52e9574",
            "max_address_length": 34,
            "maxfee_kb": 2000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Bitcore",
            "segwit": true,
            "shortcut": "BTX",
            "signed_message_header": "BitCore Signed Message:\n",
            "slip44": 160,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 102,
            "address_type_p2sh": 5,
            "bech32_prefix": "bsd",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 200,
            "cashaddr_prefix": null,
            "coin_label": "Bitsend",
            "coin_name": "Bitsend",
            "coin_shortcut": "BSD",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "0000012e1b8843ac9ce8c18603658eaf8895f99d3f5e7e1b7b1686f35e3c087a",
            "max_address_length": 34,
            "maxfee_kb": 1000000,
            "min_address_length": 27,
            "minfee_kb": 10000,
            "name": "Bitsend",
            "segwit": true,
            "shortcut": "BSD",
            "signed_message_header": "Bitsend Signed Message:\n",
            "slip44": 91,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.10",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 28,
            "address_type_p2sh": 35,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook.capricoin.org",
                "https://blockbook2.capricoin.org",
                "https://blockbook3.capricoin.org",
                "https://blockbook4.capricoin.org"
            ],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Capricoin",
            "coin_name": "Capricoin",
            "coin_shortcut": "CPC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 7,
                "High": 20,
                "Low": 1,
                "Normal": 14
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000d23fa0fc52c90893adb1181c9ddffb6c797a3e41864b9a23aa2f2981fe3",
            "max_address_length": 34,
            "maxfee_kb": 2000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Capricoin",
            "segwit": false,
            "shortcut": "CPC",
            "signed_message_header": "Capricoin Signed Message:\n",
            "slip44": 289,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.0.10",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 76,
            "address_type_p2sh": 16,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://dash1.trezor.io",
                "https://dash2.trezor.io",
                "https://dash3.trezor.io",
                "https://dash4.trezor.io",
                "https://dash5.trezor.io"
            ],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Dash",
            "coin_name": "Dash",
            "coin_shortcut": "DASH",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Dash",
            "segwit": false,
            "shortcut": "DASH",
            "signed_message_header": "DarkCoin Signed Message:\n",
            "slip44": 5,
            "support": {
                "connect": true,
                "trezor1": "1.5.2",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 50221816,
            "xpub_magic": 50221772,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 140,
            "address_type_p2sh": 19,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Dash Testnet",
            "coin_name": "Dash Testnet",
            "coin_shortcut": "tDASH",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 10000,
            "name": "Dash Testnet",
            "segwit": false,
            "shortcut": "tDASH",
            "signed_message_header": "DarkCoin Signed Message:\n",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 70615956,
            "xpub_magic": 70617039,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 30,
            "address_type_p2sh": 90,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 30,
            "cashaddr_prefix": null,
            "coin_label": "Denarius",
            "coin_name": "Denarius",
            "coin_shortcut": "DNR",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000d5dbbda01621cfc16bbc1f9bf3264d641a5dbf0de89fd0182c2c4828fcd",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 10000,
            "name": "Denarius",
            "segwit": false,
            "shortcut": "DNR",
            "signed_message_header": "Denarius Signed Message:\n",
            "slip44": 116,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 30,
            "address_type_p2sh": 63,
            "bech32_prefix": "dgb",
            "bip115": false,
            "blockbook": [
                "https://dgb1.trezor.io",
                "https://dgb2.trezor.io"
            ],
            "blocktime_seconds": 15,
            "cashaddr_prefix": null,
            "coin_label": "DigiByte",
            "coin_name": "DigiByte",
            "coin_shortcut": "DGB",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496",
            "max_address_length": 34,
            "maxfee_kb": 500000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "DigiByte",
            "segwit": true,
            "shortcut": "DGB",
            "signed_message_header": "DigiByte Signed Message:\n",
            "slip44": 20,
            "support": {
                "connect": true,
                "trezor1": "1.6.3",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 30,
            "address_type_p2sh": 22,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://doge1.trezor.io",
                "https://doge2.trezor.io",
                "https://doge3.trezor.io",
                "https://doge4.trezor.io",
                "https://doge5.trezor.io"
            ],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Dogecoin",
            "coin_name": "Dogecoin",
            "coin_shortcut": "DOGE",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 100000
            },
            "dust_limit": 10000000,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691",
            "max_address_length": 34,
            "maxfee_kb": 1000000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Dogecoin",
            "segwit": false,
            "shortcut": "DOGE",
            "signed_message_header": "Dogecoin Signed Message:\n",
            "slip44": 3,
            "support": {
                "connect": true,
                "trezor1": "1.5.2",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 49988504,
            "xpub_magic": 49990397,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 14,
            "address_type_p2sh": 5,
            "bech32_prefix": "fc",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Feathercoin",
            "coin_name": "Feathercoin",
            "coin_shortcut": "FTC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Feathercoin",
            "segwit": true,
            "shortcut": "FTC",
            "signed_message_header": "Feathercoin Signed Message:\n",
            "slip44": 8,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 76077806,
            "xpub_magic": 76069926,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 35,
            "address_type_p2sh": 94,
            "bech32_prefix": "flo",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 40,
            "cashaddr_prefix": null,
            "coin_label": "Flo",
            "coin_name": "Florincoin",
            "coin_shortcut": "FLO",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 100000,
            "name": "Flo",
            "segwit": true,
            "shortcut": "FLO",
            "signed_message_header": "Florincoin Signed Message:\n",
            "slip44": 216,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 15264107,
            "xpub_magic": 1526049,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 28471030
        },
        {
            "address_type": 36,
            "address_type_p2sh": 16,
            "bech32_prefix": "fc",
            "bip115": false,
            "blockbook": [
                "https://explorer.fujicoin.org"
            ],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Fujicoin",
            "coin_name": "Fujicoin",
            "coin_shortcut": "FJC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 20000,
                "High": 100000,
                "Low": 10000,
                "Normal": 50000
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "adb6d9cfd74075e7f91608add4bd2a2ea636f70856183086842667a1597714a0",
            "max_address_length": 34,
            "maxfee_kb": 1000000000,
            "min_address_length": 27,
            "minfee_kb": 10000000,
            "name": "Fujicoin",
            "segwit": true,
            "shortcut": "FJC",
            "signed_message_header": "FujiCoin Signed Message:\n",
            "slip44": 75,
            "support": {
                "connect": true,
                "trezor1": "1.6.1",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 38,
            "address_type_p2sh": 10,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook.gincoin.io"
            ],
            "blocktime_seconds": 120,
            "cashaddr_prefix": null,
            "coin_label": "GIN",
            "coin_name": "Gincoin",
            "coin_shortcut": "GIN",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000cd6bde619b2c3b23ad2e384328a450a37fa28731debf748c3b17f91f97d",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "GIN",
            "segwit": false,
            "shortcut": "GIN",
            "signed_message_header": "DarkCoin Signed Message:\n",
            "slip44": 2000,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 50221816,
            "xpub_magic": 50221772,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 38,
            "address_type_p2sh": 62,
            "bech32_prefix": "game",
            "bip115": false,
            "blockbook": [
                "https://blockbook.gamecredits.network"
            ],
            "blocktime_seconds": 90,
            "cashaddr_prefix": null,
            "coin_label": "GameCredits",
            "coin_name": "GameCredits",
            "coin_shortcut": "GAME",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "91ec5f25ee9a0ffa1af7d4da4db9a552228dd2dc77cdb15b738be4e1f55f30ee",
            "max_address_length": 34,
            "maxfee_kb": 5000000,
            "min_address_length": 27,
            "minfee_kb": 100000,
            "name": "GameCredits",
            "segwit": true,
            "shortcut": "GAME",
            "signed_message_header": "GameCredits Signed Message:\n",
            "slip44": 101,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 27108450,
            "xpub_magic": 27106558,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 28471030
        },
        {
            "address_type": 8329,
            "address_type_p2sh": 8342,
            "bech32_prefix": null,
            "bip115": true,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Horizen",
            "coin_name": "Horizen",
            "coin_shortcut": "ZEN",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602",
            "max_address_length": 95,
            "maxfee_kb": 2000000,
            "min_address_length": 35,
            "minfee_kb": 1000,
            "name": "Horizen",
            "segwit": false,
            "shortcut": "ZEN",
            "signed_message_header": "Zcash Signed Message:\n",
            "slip44": 121,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 60,
            "address_type_p2sh": 85,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Komodo",
            "coin_name": "Komodo",
            "coin_shortcut": "KMD",
            "consensus_branch_id": {
                "1": 0,
                "2": 0,
                "3": 1537743641,
                "4": 1991772603
            },
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "027e3758c3a65b12aa1046462b486d0a63bfa1beae327897f56c5cfb7daaae71",
            "max_address_length": 34,
            "maxfee_kb": 1000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Komodo",
            "segwit": false,
            "shortcut": "KMD",
            "signed_message_header": "Komodo Signed Message:\n",
            "slip44": 141,
            "support": {
                "connect": true,
                "trezor1": "1.8.0",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 6198,
            "address_type_p2sh": 6203,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Koto",
            "coin_name": "Koto",
            "coin_shortcut": "KOTO",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "6d424c350729ae633275d51dc3496e16cd1b1d195c164da00f39c499a2e9959e",
            "max_address_length": 95,
            "maxfee_kb": 1000000,
            "min_address_length": 35,
            "minfee_kb": 1000,
            "name": "Koto",
            "segwit": false,
            "shortcut": "KOTO",
            "signed_message_header": "Koto Signed Message:\n",
            "slip44": 510,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 48,
            "address_type_p2sh": 50,
            "bech32_prefix": "ltc",
            "bip115": false,
            "blockbook": [
                "https://ltc1.trezor.io",
                "https://ltc2.trezor.io",
                "https://ltc3.trezor.io",
                "https://ltc4.trezor.io",
                "https://ltc5.trezor.io"
            ],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Litecoin",
            "coin_name": "Litecoin",
            "coin_shortcut": "LTC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 100000,
            "name": "Litecoin",
            "segwit": true,
            "shortcut": "LTC",
            "signed_message_header": "Litecoin Signed Message:\n",
            "slip44": 2,
            "support": {
                "connect": true,
                "trezor1": "1.5.2",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 27106558,
            "xpub_magic": 27108450,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 28471030
        },
        {
            "address_type": 111,
            "address_type_p2sh": 58,
            "bech32_prefix": "tltc",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Litecoin Testnet",
            "coin_name": "Litecoin Testnet",
            "coin_shortcut": "tLTC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Litecoin Testnet",
            "segwit": true,
            "shortcut": "tLTC",
            "signed_message_header": "Litecoin Signed Message:\n",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 70615956,
            "xpub_magic": 70617039,
            "xpub_magic_segwit_native": 73342198,
            "xpub_magic_segwit_p2sh": 71979618
        },
        {
            "address_type": 50,
            "address_type_p2sh": 5,
            "bech32_prefix": "mec",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Megacoin",
            "coin_name": "Megacoin",
            "coin_shortcut": "MEC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Low": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "7520788e2d99eec7cf6cf7315577e1268e177fff94cb0a7caf6a458ceeea9ac2",
            "max_address_length": 34,
            "maxfee_kb": 1000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Megacoin",
            "segwit": true,
            "shortcut": "MEC",
            "signed_message_header": "MegaCoin Signed Message:\n",
            "slip44": 217,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.10",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 50,
            "address_type_p2sh": 55,
            "bech32_prefix": "mona",
            "bip115": false,
            "blockbook": [
                "https://blockbook.electrum-mona.org"
            ],
            "blocktime_seconds": 90,
            "cashaddr_prefix": null,
            "coin_label": "Monacoin",
            "coin_name": "Monacoin",
            "coin_shortcut": "MONA",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "ff9f1c0116d19de7c9963845e129f9ed1bfc0b376eb54fd7afa42e0d418c8bb6",
            "max_address_length": 34,
            "maxfee_kb": 5000000,
            "min_address_length": 27,
            "minfee_kb": 100000,
            "name": "Monacoin",
            "segwit": true,
            "shortcut": "MONA",
            "signed_message_header": "Monacoin Signed Message:\n",
            "slip44": 22,
            "support": {
                "connect": true,
                "trezor1": "1.6.0",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 16,
            "address_type_p2sh": 76,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook.monetaryunit.org"
            ],
            "blocktime_seconds": 40,
            "cashaddr_prefix": null,
            "coin_label": "MonetaryUnit",
            "coin_name": "MonetaryUnit",
            "coin_shortcut": "MUE",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "0b58ed450b3819ca54ab0054c4d220ca4f887d21c9e55d2a333173adf76d987f",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "MonetaryUnit",
            "segwit": false,
            "shortcut": "MUE",
            "signed_message_header": "MonetaryUnit Signed Message:\n",
            "slip44": 31,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 50,
            "address_type_p2sh": 9,
            "bech32_prefix": "my",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Myriad",
            "coin_name": "Myriad",
            "coin_shortcut": "XMY",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000ffde4c020b5938441a0ea3d314bf619eff0b38f32f78f7583cffa1ea485",
            "max_address_length": 34,
            "maxfee_kb": 2000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Myriad",
            "segwit": true,
            "shortcut": "XMY",
            "signed_message_header": "Myriadcoin Signed Message:\n",
            "slip44": 90,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 38,
            "address_type_p2sh": 53,
            "bech32_prefix": "nix",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 120,
            "cashaddr_prefix": null,
            "coin_label": "NIX",
            "coin_name": "NIX",
            "coin_shortcut": "NIX",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "dd28ad86def767c3cfc34267a950d871fc7462bc57ea4a929fc3596d9b598e41",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 0,
            "name": "NIX",
            "segwit": true,
            "shortcut": "NIX",
            "signed_message_header": "NIX Signed Message:\n",
            "slip44": 400,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 52,
            "address_type_p2sh": 5,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://nmc1.trezor.io",
                "https://nmc2.trezor.io"
            ],
            "blocktime_seconds": 600,
            "cashaddr_prefix": null,
            "coin_label": "Namecoin",
            "coin_name": "Namecoin",
            "coin_shortcut": "NMC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 2940,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770",
            "max_address_length": 34,
            "maxfee_kb": 10000000,
            "min_address_length": 27,
            "minfee_kb": 100000,
            "name": "Namecoin",
            "segwit": false,
            "shortcut": "NMC",
            "signed_message_header": "Namecoin Signed Message:\n",
            "slip44": 7,
            "support": {
                "connect": true,
                "trezor1": "1.5.2",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 30,
            "address_type_p2sh": 13,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook.pivx.link"
            ],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "PIVX",
            "coin_name": "PIVX",
            "coin_shortcut": "PIVX",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 100,
            "name": "PIVX",
            "segwit": false,
            "shortcut": "PIVX",
            "signed_message_header": "DarkNet Signed Message:\n",
            "slip44": 119,
            "support": {
                "connect": true,
                "trezor1": "1.8.0",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 35729707,
            "xpub_magic": 36513075,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 139,
            "address_type_p2sh": 19,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook-testnet.pivx.link"
            ],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "PIVX Testnet",
            "coin_name": "PIVX Testnet",
            "coin_shortcut": "tPIVX",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 100,
            "name": "PIVX Testnet",
            "segwit": false,
            "shortcut": "tPIVX",
            "signed_message_header": "DarkNet Signed Message:\n",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.8.0",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 981489719,
            "xpub_magic": 981492128,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 47,
            "address_type_p2sh": 22,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Pesetacoin",
            "coin_name": "Pesetacoin",
            "coin_shortcut": "PTC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 10000000,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "edfe5830b53251bfff733600b1cd5c192e761c011b055f07924634818c906438",
            "max_address_length": 34,
            "maxfee_kb": 1000000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Pesetacoin",
            "segwit": false,
            "shortcut": "PTC",
            "signed_message_header": "Pesetacoin Signed Message:\n",
            "slip44": 109,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "xprv_magic": 76079604,
            "xpub_magic": 76071982,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 55,
            "address_type_p2sh": 56,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook.polispay.org"
            ],
            "blocktime_seconds": 120,
            "cashaddr_prefix": null,
            "coin_label": "Polis",
            "coin_name": "Polis",
            "coin_shortcut": "POLIS",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 5460,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "000009701eb781a8113b1af1d814e2f060f6408a2c990db291bc5108a1345c1e",
            "max_address_length": 34,
            "maxfee_kb": 100000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Polis",
            "segwit": false,
            "shortcut": "POLIS",
            "signed_message_header": "Polis Signed Message:\n",
            "slip44": 1997,
            "support": {
                "connect": true,
                "trezor1": "1.8.2",
                "trezor2": "2.1.1",
                "webwallet": false
            },
            "xprv_magic": 65165637,
            "xpub_magic": 65166718,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 23,
            "address_type_p2sh": 83,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Primecoin",
            "coin_name": "Primecoin",
            "coin_shortcut": "XPM",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "963d17ba4dc753138078a2f56afb3af9674e2546822badff26837db9a0152106",
            "max_address_length": 35,
            "maxfee_kb": 1000000,
            "min_address_length": 26,
            "minfee_kb": 1000,
            "name": "Primecoin",
            "segwit": false,
            "shortcut": "XPM",
            "signed_message_header": "Primecoin Signed Message:\n",
            "slip44": 24,
            "support": {
                "connect": true,
                "trezor1": "1.8.0",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 60,
            "address_type_p2sh": 122,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook.ravencoin.org"
            ],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Ravencoin",
            "coin_name": "Ravencoin",
            "coin_shortcut": "RVN",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Low": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "0000006b444bc2f2ffe627be9d9e7e7a0730000870ef6eb6da46c8eae389df90",
            "max_address_length": 34,
            "maxfee_kb": 2000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Ravencoin",
            "segwit": false,
            "shortcut": "RVN",
            "signed_message_header": "Raven Signed Message:\n",
            "slip44": 175,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.10",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 76,
            "address_type_p2sh": 16,
            "bech32_prefix": "xc",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 60,
            "cashaddr_prefix": null,
            "coin_label": "Stakenet",
            "coin_name": "Stakenet",
            "coin_shortcut": "XSN",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 70,
                "High": 200,
                "Low": 10,
                "Normal": 140
            },
            "dust_limit": 1000,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00000c822abdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34",
            "max_address_length": 47,
            "maxfee_kb": 2000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Stakenet",
            "segwit": true,
            "shortcut": "XSN",
            "signed_message_header": "DarkCoin Signed Message:\n",
            "slip44": 199,
            "support": {
                "connect": true,
                "trezor1": "1.8.0",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 130,
            "address_type_p2sh": 30,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://blockbook.flurbo.xyz",
                "https://blockbook.unobtanium.uno"
            ],
            "blocktime_seconds": 30,
            "cashaddr_prefix": null,
            "coin_label": "Unobtanium",
            "coin_name": "Unobtanium",
            "coin_shortcut": "UNO",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 50,
                "High": 160,
                "Low": 10,
                "Normal": 100
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "000004c2fc5fffb810dccc197d603690099a68305232e552d96ccbe8e2c52b75",
            "max_address_length": 34,
            "maxfee_kb": 2000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Unobtanium",
            "segwit": false,
            "shortcut": "UNO",
            "signed_message_header": "Unobtanium Signed Message:\n",
            "slip44": 92,
            "support": {
                "connect": true,
                "trezor1": "1.8.4",
                "trezor2": "2.1.6",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 71,
            "address_type_p2sh": 5,
            "bech32_prefix": "vtc",
            "bip115": false,
            "blockbook": [
                "https://vtc1.trezor.io",
                "https://vtc2.trezor.io",
                "https://vtc3.trezor.io",
                "https://vtc4.trezor.io",
                "https://vtc5.trezor.io"
            ],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Vertcoin",
            "coin_name": "Vertcoin",
            "coin_shortcut": "VTC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 1000
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "4d96a915f49d40b1e5c2844d1ee2dccb90013a990ccea12c492d22110489f0c4",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 100000,
            "name": "Vertcoin",
            "segwit": true,
            "shortcut": "VTC",
            "signed_message_header": "Vertcoin Signed Message:\n",
            "slip44": 28,
            "support": {
                "connect": true,
                "trezor1": "1.6.1",
                "trezor2": "2.0.5",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 71,
            "address_type_p2sh": 33,
            "bech32_prefix": "via",
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 24,
            "cashaddr_prefix": null,
            "coin_label": "Viacoin",
            "coin_name": "Viacoin",
            "coin_shortcut": "VIA",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 7000,
                "High": 20000,
                "Low": 1000,
                "Normal": 14000
            },
            "dust_limit": 54600,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "4e9b54001f9976049830128ec0331515eaabe35a70970d79971da1539a400ba1",
            "max_address_length": 34,
            "maxfee_kb": 40000000,
            "min_address_length": 27,
            "minfee_kb": 1000,
            "name": "Viacoin",
            "segwit": true,
            "shortcut": "VIA",
            "signed_message_header": "Viacoin Signed Message:\n",
            "slip44": 14,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": 78792518,
            "xpub_magic_segwit_p2sh": 77429938
        },
        {
            "address_type": 7352,
            "address_type_p2sh": 7357,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "ZClassic",
            "coin_name": "ZClassic",
            "coin_shortcut": "ZCL",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602",
            "max_address_length": 95,
            "maxfee_kb": 1000000,
            "min_address_length": 35,
            "minfee_kb": 1000,
            "name": "ZClassic",
            "segwit": false,
            "shortcut": "ZCL",
            "signed_message_header": "Zcash Signed Message:\n",
            "slip44": 147,
            "support": {
                "connect": true,
                "trezor1": "1.8.0",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 7352,
            "address_type_p2sh": 7357,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [
                "https://zec1.trezor.io",
                "https://zec2.trezor.io",
                "https://zec3.trezor.io",
                "https://zec4.trezor.io",
                "https://zec5.trezor.io"
            ],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Zcash",
            "coin_name": "Zcash",
            "coin_shortcut": "ZEC",
            "consensus_branch_id": {
                "1": 0,
                "2": 0,
                "3": 1537743641,
                "4": 1991772603
            },
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08",
            "max_address_length": 95,
            "maxfee_kb": 1000000,
            "min_address_length": 35,
            "minfee_kb": 1000,
            "name": "Zcash",
            "segwit": false,
            "shortcut": "ZEC",
            "signed_message_header": "Zcash Signed Message:\n",
            "slip44": 133,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 7461,
            "address_type_p2sh": 7354,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 150,
            "cashaddr_prefix": null,
            "coin_label": "Zcash Testnet",
            "coin_name": "Zcash Testnet",
            "coin_shortcut": "TAZ",
            "consensus_branch_id": {
                "1": 0,
                "2": 0,
                "3": 1537743641,
                "4": 1991772603
            },
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Normal": 10
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38",
            "max_address_length": 95,
            "maxfee_kb": 10000000,
            "min_address_length": 35,
            "minfee_kb": 1000,
            "name": "Zcash Testnet",
            "segwit": false,
            "shortcut": "TAZ",
            "signed_message_header": "Zcash Signed Message:\n",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 70615956,
            "xpub_magic": 70617039,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 82,
            "address_type_p2sh": 7,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 600,
            "cashaddr_prefix": null,
            "coin_label": "Zcoin",
            "coin_name": "Zcoin",
            "coin_shortcut": "XZC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 10,
                "High": 200,
                "Low": 1,
                "Normal": 100
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233",
            "max_address_length": 34,
            "maxfee_kb": 1000000,
            "min_address_length": 27,
            "minfee_kb": 0,
            "name": "Zcoin",
            "segwit": false,
            "shortcut": "XZC",
            "signed_message_header": "Zcoin Signed Message:\n",
            "slip44": 136,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "xprv_magic": 76066276,
            "xpub_magic": 76067358,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        },
        {
            "address_type": 65,
            "address_type_p2sh": 178,
            "bech32_prefix": null,
            "bip115": false,
            "blockbook": [],
            "blocktime_seconds": 600,
            "cashaddr_prefix": null,
            "coin_label": "Zcoin Testnet",
            "coin_name": "Zcoin Testnet",
            "coin_shortcut": "tXZC",
            "consensus_branch_id": null,
            "curve_name": "secp256k1",
            "decred": false,
            "default_fee_b": {
                "Economy": 10,
                "High": 200,
                "Low": 1,
                "Normal": 100
            },
            "dust_limit": 546,
            "force_bip143": false,
            "fork_id": null,
            "hash_genesis_block": "7ac038c193c2158c428c59f9ae0c02a07115141c6e9dc244ae96132e99b4e642",
            "max_address_length": 35,
            "maxfee_kb": 1000000,
            "min_address_length": 27,
            "minfee_kb": 0,
            "name": "Zcoin Testnet",
            "segwit": false,
            "shortcut": "tXZC",
            "signed_message_header": "Zcoin Signed Message:\n",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "xprv_magic": 70615956,
            "xpub_magic": 70617039,
            "xpub_magic_segwit_native": null,
            "xpub_magic_segwit_p2sh": null
        }
    ],
    "erc20": [],
    "eth": [
        {
            "blockbook": [
                "https://eth1.trezor.io",
                "https://eth2.trezor.io"
            ],
            "chain": "eth",
            "chain_id": 1,
            "name": "Ethereum",
            "rskip60": false,
            "shortcut": "ETH",
            "slip44": 60,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "url": "https://www.ethereum.org"
        },
        {
            "blockbook": [],
            "chain": "exp",
            "chain_id": 2,
            "name": "Expanse",
            "rskip60": false,
            "shortcut": "EXP",
            "slip44": 40,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://expanse.tech"
        },
        {
            "blockbook": [
                "https://ropsten1.trezor.io",
                "https://ropsten2.trezor.io"
            ],
            "chain": "rop",
            "chain_id": 3,
            "name": "Ethereum Testnet Ropsten",
            "rskip60": false,
            "shortcut": "tROP",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://www.ethereum.org"
        },
        {
            "blockbook": [],
            "chain": "rin",
            "chain_id": 4,
            "name": "Ethereum Testnet Rinkeby",
            "rskip60": false,
            "shortcut": "tRIN",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://rinkeby.io"
        },
        {
            "blockbook": [],
            "chain": "ubq",
            "chain_id": 8,
            "name": "Ubiq",
            "rskip60": false,
            "shortcut": "UBQ",
            "slip44": 108,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://ubiqsmart.com"
        },
        {
            "blockbook": [],
            "chain": "etsc",
            "chain_id": 28,
            "name": "Ethereum Social",
            "rskip60": false,
            "shortcut": "ETSC",
            "slip44": 1128,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://ethereumsocial.kr"
        },
        {
            "blockbook": [],
            "chain": "rsk",
            "chain_id": 30,
            "name": "RSK",
            "rskip60": true,
            "shortcut": "RBTC",
            "slip44": 137,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://www.rsk.co"
        },
        {
            "blockbook": [],
            "chain": "trsk",
            "chain_id": 31,
            "name": "RSK Testnet",
            "rskip60": true,
            "shortcut": "tRBTC",
            "slip44": 37310,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://www.rsk.co"
        },
        {
            "blockbook": [],
            "chain": "kov",
            "chain_id": 42,
            "name": "Ethereum Testnet Kovan",
            "rskip60": false,
            "shortcut": "tKOV",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://www.ethereum.org"
        },
        {
            "blockbook": [],
            "chain": "go",
            "chain_id": 60,
            "name": "GoChain",
            "rskip60": false,
            "shortcut": "GO",
            "slip44": 6060,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://gochain.io"
        },
        {
            "blockbook": [
                "https://etc1.trezor.io",
                "https://etc2.trezor.io"
            ],
            "chain": "etc",
            "chain_id": 61,
            "name": "Ethereum Classic",
            "rskip60": false,
            "shortcut": "ETC",
            "slip44": 61,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": true
            },
            "url": "https://ethereumclassic.github.io"
        },
        {
            "blockbook": [],
            "chain": "tetc",
            "chain_id": 62,
            "name": "Ethereum Classic Testnet",
            "rskip60": false,
            "shortcut": "tETC",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://ethereumclassic.github.io"
        },
        {
            "blockbook": [],
            "chain": "ella",
            "chain_id": 64,
            "name": "Ellaism",
            "rskip60": false,
            "shortcut": "ELLA",
            "slip44": 163,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://ellaism.org"
        },
        {
            "blockbook": [],
            "chain": "mix",
            "chain_id": 76,
            "name": "Mix",
            "rskip60": false,
            "shortcut": "MIX",
            "slip44": 76,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.10",
                "webwallet": false
            },
            "url": "https://www.mix-blockchain.org"
        },
        {
            "blockbook": [],
            "chain": "clo",
            "chain_id": 820,
            "name": "Callisto",
            "rskip60": false,
            "shortcut": "CLO",
            "slip44": 820,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://callisto.network"
        },
        {
            "blockbook": [],
            "chain": "ath",
            "chain_id": 1620,
            "name": "Atheios",
            "rskip60": false,
            "shortcut": "ATH",
            "slip44": 1620,
            "support": {
                "connect": true,
                "trezor1": "1.6.3",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "url": "https://atheios.com"
        },
        {
            "blockbook": [],
            "chain": "egem",
            "chain_id": 1987,
            "name": "EtherGem",
            "rskip60": false,
            "shortcut": "EGEM",
            "slip44": 1987,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://egem.io"
        },
        {
            "blockbook": [],
            "chain": "eosc",
            "chain_id": 2018,
            "name": "EOS Classic",
            "rskip60": false,
            "shortcut": "EOSC",
            "slip44": 2018,
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "url": "https://eos-classic.io"
        },
        {
            "blockbook": [],
            "chain": "reosc",
            "chain_id": 2894,
            "name": "REOSC Ecosystem",
            "rskip60": false,
            "shortcut": "REOSC",
            "slip44": 2894,
            "support": {
                "connect": true,
                "trezor1": "1.7.2",
                "trezor2": "2.0.11",
                "webwallet": false
            },
            "url": "https://reosc.io"
        },
        {
            "blockbook": [],
            "chain": "esn",
            "chain_id": 31102,
            "name": "Ethersocial Network",
            "rskip60": false,
            "shortcut": "ESN",
            "slip44": 31102,
            "support": {
                "connect": true,
                "trezor1": "1.6.3",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "url": "https://ethersocial.org"
        },
        {
            "blockbook": [],
            "chain": "teo",
            "chain_id": 33416,
            "name": "Trust ETH reOrigin",
            "rskip60": false,
            "shortcut": "TEO",
            "slip44": 33416,
            "support": {
                "connect": true,
                "trezor1": "1.8.2",
                "trezor2": "2.1.1",
                "webwallet": false
            },
            "url": "https://tao.foundation"
        },
        {
            "blockbook": [],
            "chain": "akroma",
            "chain_id": 200625,
            "name": "Akroma",
            "rskip60": false,
            "shortcut": "AKA",
            "slip44": 200625,
            "support": {
                "connect": true,
                "trezor1": "1.6.3",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "url": "https://akroma.io"
        },
        {
            "blockbook": [],
            "chain": "etho",
            "chain_id": 1313114,
            "name": "Ether-1",
            "rskip60": false,
            "shortcut": "ETHO",
            "slip44": 1313114,
            "support": {
                "connect": true,
                "trezor1": "1.6.3",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "url": "https://ether1.org"
        },
        {
            "blockbook": [],
            "chain": "music",
            "chain_id": 7762959,
            "name": "Musicoin",
            "rskip60": false,
            "shortcut": "MUSIC",
            "slip44": 184,
            "support": {
                "connect": true,
                "trezor1": "1.6.3",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "url": "https://musicoin.org"
        },
        {
            "blockbook": [],
            "chain": "pirl",
            "chain_id": 3125659152,
            "name": "Pirl",
            "rskip60": false,
            "shortcut": "PIRL",
            "slip44": 164,
            "support": {
                "connect": true,
                "trezor1": "1.6.3",
                "trezor2": "2.0.8",
                "webwallet": false
            },
            "url": "https://pirl.io"
        }
    ],
    "misc": [
        {
            "blockchain_link": null,
            "curve": "ed25519",
            "decimals": 6,
            "name": "Cardano",
            "shortcut": "ADA",
            "slip44": 1815,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.0.8",
                "webwallet": false
            }
        },
        {
            "blockchain_link": null,
            "curve": "secp256k1",
            "decimals": 8,
            "name": "Binance Chain",
            "shortcut": "BNB",
            "slip44": 714,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.1.5",
                "webwallet": false
            }
        },
        {
            "blockchain_link": null,
            "curve": "secp256k1",
            "decimals": 4,
            "name": "EOS",
            "shortcut": "EOS",
            "slip44": 194,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.1.1",
                "webwallet": false
            }
        },
        {
            "blockchain_link": null,
            "curve": "ed25519",
            "decimals": 8,
            "name": "Lisk",
            "shortcut": "LSK",
            "slip44": 134,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.7",
                "webwallet": false
            }
        },
        {
            "blockchain_link": {
                "type": "ripple",
                "url": [
                    "wss://s.altnet.rippletest.net"
                ]
            },
            "curve": "secp256k1",
            "decimals": 6,
            "name": "Ripple Testnet",
            "shortcut": "tXRP",
            "slip44": 1,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.0.8",
                "webwallet": false
            }
        },
        {
            "blockchain_link": null,
            "curve": "ed25519",
            "decimals": 7,
            "name": "Stellar",
            "shortcut": "XLM",
            "slip44": 148,
            "support": {
                "connect": true,
                "trezor1": "1.7.1",
                "trezor2": "2.0.8",
                "webwallet": false
            }
        },
        {
            "blockchain_link": {
                "type": "ripple",
                "url": [
                    "wss://s1.ripple.com",
                    "wss://s-east.ripple.com",
                    "wss://s-west.ripple.com"
                ]
            },
            "curve": "secp256k1",
            "decimals": 6,
            "name": "Ripple",
            "shortcut": "XRP",
            "slip44": 144,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.0.8",
                "webwallet": false
            }
        },
        {
            "blockchain_link": null,
            "curve": "ed25519",
            "decimals": 6,
            "name": "Tezos",
            "shortcut": "XTZ",
            "slip44": 1729,
            "support": {
                "connect": true,
                "trezor1": false,
                "trezor2": "2.0.8",
                "webwallet": false
            }
        }
    ],
    "nem": [
        {
            "divisibility": 6,
            "mosaic": "xem",
            "name": "NEM",
            "namespace": "nem",
            "shortcut": "XEM",
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "ticker": "XEM"
        },
        {
            "divisibility": 6,
            "fee": 10,
            "levy": "MosaicLevy_Percentile",
            "levy_mosaic": "coin",
            "levy_namespace": "dim",
            "mosaic": "coin",
            "name": "DIMCOIN",
            "namespace": "dim",
            "networks": [
                104
            ],
            "shortcut": "DIM",
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "ticker": "DIM"
        },
        {
            "divisibility": 6,
            "mosaic": "token",
            "name": "DIM TOKEN",
            "namespace": "dim",
            "networks": [
                104
            ],
            "shortcut": "DIMTOK",
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "ticker": "DIMTOK"
        },
        {
            "divisibility": 0,
            "mosaic": "breeze-token",
            "name": "Breeze Token",
            "namespace": "breeze",
            "networks": [
                104
            ],
            "shortcut": "BREEZE",
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "ticker": "BREEZE"
        },
        {
            "divisibility": 0,
            "mosaic": "heart",
            "name": "PacNEM Game Credits",
            "namespace": "pacnem",
            "networks": [
                104
            ],
            "shortcut": "PAC:HRT",
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "ticker": "PAC:HRT"
        },
        {
            "divisibility": 6,
            "fee": 100,
            "levy": "MosaicLevy_Percentile",
            "levy_mosaic": "xem",
            "levy_namespace": "nem",
            "mosaic": "cheese",
            "name": "PacNEM Score Tokens",
            "namespace": "pacnem",
            "networks": [
                104
            ],
            "shortcut": "PAC:CHS",
            "support": {
                "connect": true,
                "trezor1": "1.6.2",
                "trezor2": "2.0.7",
                "webwallet": false
            },
            "ticker": "PAC:CHS"
        }
    ]
}

================================================
FILE: config/default.json
================================================
{
    "some_ticker (e.g. 'DASH')": {
        "ADMIN_PUBLIC_KEY": "",
        "ADMIN_PRIVATE_KEY": ""
    },
    

    "KEAGATE_API_KEY": "API-KEY",
    "IP_WHITELIST": [],

    "TRANSACTION_TIMEOUT": 1200000,
    "TRANSACTION_MIN_REFRESH_TIME": 30000,
    "TRANSACTION_SLIPPAGE_TOLERANCE": 0.02,
    "BLOCKBOOK_RETRY_DELAY": 6000,
    
    "MONGO_CONNECTION_STRING": "mongodb://localhost:27017",
    "MONGO_KEAGATE_DB": "keagate",

    "INVOICE_ENC_KEY": "D(G+KbPeShVmYq3s6v9y$B&E)H@McQfT",

    "IS_DEV": false,
    "USE_SO_CHAIN": true,
    "PORT": 8081
}

================================================
FILE: package.json
================================================
{
  "name": "Keagate",
  "version": "1.0.0",
  "description": "\"# Keagate\"",
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "build": "pnpm --filter \"*\" prebuild && tsc --build ./tsconfig.project.json",
    "start:pm2-stop": "pm2 stop Keagate || true",
    "start:pm2-del": "pm2 del Keagate || true",
    "start:build-invoice-client": "pnpm --filter \"invoice-client\" build",
    "start:pm2-start": "pm2 start packages/backend/build/index.js --name \"Keagate\" --time && pm2 save",
    "start": "npm-run-all start:pm2-stop start:pm2-del start:build-invoice-client start:pm2-start",
    "clean": "pnpm --filter \"*\" clean && shx rm -rf pnpm-lock.yaml node_modules",
    "dev": "npm-run-all build --parallel dev:*",
    "dev:backend": "pnpm --filter \"backend\" dev",
    "dev:invoice-client": "pnpm --filter \"invoice-client\" dev",
    "prettier-format": "prettier --config prettier.config.js 'packages/*/src/**/*{.ts,.tsx,.js,.jsx}' --write",
    "find-circular-deps": "pnpx madge --circular --extensions ts,tsx packages/"
  },
  "author": "dilan-dio4",
  "license": "MIT",
  "devDependencies": {
    "@types/big.js": "^6.1.3",
    "@types/multicoin-address-validator": "^0.5.0",
    "@types/node": "^18.0.1",
    "@typescript-eslint/eslint-plugin": "^4.14.2",
    "@typescript-eslint/parser": "^4.14.2",
    "eslint": "^7.19.0",
    "npm-run-all": "^4.1.5",
    "prettier": "^2.7.1",
    "shx": "^0.3.4",
    "ts-node-dev": "^2.0.0",
    "typescript": "^5.4.5"
  }
}


================================================
FILE: packages/api-providers/package.json
================================================
{
  "name": "@keagate/api-providers",
  "version": "1.0.0",
  "description": "\"# keagate\"",
  "main": "build/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "prebuild": "shx rm -f tsconfig.package.tsbuildinfo",
    "build": "tsc --build ./tsconfig.package.json",
    "clean": "shx rm -rf build pnpm-lock.yaml node_modules tsconfig.package.tsbuildinfo"
  },
  "dependencies": {
    "@keagate/common": "^1.0.0",
    "big.js": "^6.2.0"
  }
}


================================================
FILE: packages/api-providers/src/GenericProvider.ts
================================================
import { AvailableCurrencies } from '@keagate/common';

export default abstract class GenericProvider {
    public supportedCurrencies: AvailableCurrencies[];
    constructor(..._: any[]) {
        null;
    }
    abstract getBalance(currency: AvailableCurrencies, address: string): Promise<{ result: { confirmedBalance: number; unconfirmedBalance?: number } }>;
    public sendTransaction?(currency: AvailableCurrencies, hexTransaction: string): Promise<{ result: string }>;
}


================================================
FILE: packages/api-providers/src/NowNodesProvider.ts
================================================
import GenericProvider from './GenericProvider';
import { AvailableCurrencies, fGet, fPost } from '@keagate/common';
import units from './units';
import Big from 'big.js';

// https://documenter.getpostman.com/view/13630829/TVmFkLwy#cebd6a63-13bc-4ba1-81f7-360c88871b90

export default class NowNodesProvider extends GenericProvider {
    public supportedCurrencies: AvailableCurrencies[] = ['LTC', 'BTC'];

    constructor(public apiKey: string, public currenciesToRpcUrls: Partial<Record<AvailableCurrencies, string>>) {
        super();
    }

    async getBalance(currency: AvailableCurrencies, address: string): Promise<{ result: { confirmedBalance: number; unconfirmedBalance?: number } }> {
        if (!this.supportedCurrencies.includes(currency)) {
            throw new Error('Currency not supported');
        }

        const { balance } = await fGet(`https://${currency}book.nownodes.io/api/v2/address/${address}`, {
            'api-key': this.apiKey,
        });
        const bigBalanceSatoshiLike = Big(balance);
        let confirmedBalance: Big;
        if ((currency as any) === 'DASH') {
            confirmedBalance = bigBalanceSatoshiLike.times(Big(units.dash.duff));
        } else if (currency === 'LTC') {
            confirmedBalance = bigBalanceSatoshiLike.times(Big(units.ltc.litoshi));
        } else if (currency === 'BTC') {
            confirmedBalance = bigBalanceSatoshiLike.times(Big(units.btc.satoshi));
        }
        return {
            result: {
                confirmedBalance: confirmedBalance.toNumber(),
                unconfirmedBalance: undefined,
            },
        };
    }

    async sendTransaction(currency: AvailableCurrencies, hexTransaction: string): Promise<{ result: string }> {
        if (!this.supportedCurrencies.includes(currency)) {
            throw new Error('Currency not supported');
        }

        // TODO: revert to getblocks
        try {
            const { result, error } = await fPost(
                `https://${currency}.nownodes.io`,
                {
                    jsonrpc: '2.0',
                    method: 'sendrawtransaction',
                    params: [hexTransaction],
                    API_key: this.apiKey,
                    id: 'test',
                },
                {
                    'Content-Type': 'application/json',
                },
            );

            console.log(hexTransaction, result, error);

            if (result === null) {
                throw new Error(error);
            }
            return { result };
        } catch (error) {
            console.error(JSON.stringify(error));
        }
    }
}


================================================
FILE: packages/api-providers/src/QuickNodeProvider.ts
================================================


================================================
FILE: packages/api-providers/src/SoChainProvider.ts
================================================
import GenericProvider from './GenericProvider';
import { AvailableCurrencies, fGet } from '@keagate/common';

// https://documenter.getpostman.com/view/13630829/TVmFkLwy#cebd6a63-13bc-4ba1-81f7-360c88871b90

export default class SoChainProvider extends GenericProvider {
    public supportedCurrencies: AvailableCurrencies[] = ['LTC', 'BTC'];

    async getBalance(currency: AvailableCurrencies, address: string): Promise<{ result: { confirmedBalance: number; unconfirmedBalance?: number } }> {
        if (!this.supportedCurrencies.includes(currency)) {
            throw new Error('Currency not supported');
        }

        const {
            data: { confirmed_balance, unconfirmed_balance },
        } = await fGet(`https://chain.so/api/v2/get_address_balance/${currency.toUpperCase()}/${address}`);
        return {
            result: {
                confirmedBalance: +confirmed_balance,
                unconfirmedBalance: +unconfirmed_balance,
            },
        };
    }
}


================================================
FILE: packages/api-providers/src/TatumProvider.ts
================================================
import GenericProvider from './GenericProvider';
import { AvailableCurrencies, fGet, fPost, currencies } from '@keagate/common';
import units from './units';
import Big from 'big.js';

// https://documenter.getpostman.com/view/13630829/TVmFkLwy#cebd6a63-13bc-4ba1-81f7-360c88871b90

export default class TatumProvider extends GenericProvider {
    public supportedCurrencies: AvailableCurrencies[] = ['LTC', 'BTC'];

    constructor(public apiKey: string, public location: 'eu1' | 'us-west1') {
        super();
    }

    async getBalance(currency: AvailableCurrencies, address: string): Promise<{ result: { confirmedBalance: number; unconfirmedBalance?: number } }> {
        if (!this.supportedCurrencies.includes(currency)) {
            throw new Error('Currency not supported');
        }

        let confirmedBalance: Big;

        if (currency === 'BTC' || currency === 'LTC') {
            const { outgoing, incoming } = await fGet(
                `https://api-${this.location}.tatum.io/v3/${currencies[currency].name.toLowerCase()}/address/balance/${address}`,
                {
                    'x-api-key': this.apiKey,
                },
            );
            const bigBalanceSatoshiLike = Big(incoming).minus(Big(outgoing));
            if (currency === 'BTC') {
                confirmedBalance = bigBalanceSatoshiLike.times(Big(units.btc.satoshi));
            } else if (currency === 'LTC') {
                confirmedBalance = bigBalanceSatoshiLike.times(Big(units.ltc.litoshi));
            }
        }
        // else if (currency === 'ADA') {
        //     const {
        //         summary: { assetBalances },
        //     } = await fGet(`https://api-${this.location}.tatum.io/v3/${currency}/account/${address}`, {
        //         'x-api-key': this.apiKey,
        //     });

        //     for (const currAsset of assetBalances) {
        //         if (currAsset.asset.assetId === 'ada') {
        //             confirmedBalance = Big(currAsset.quantity).times(Big(units.ada.lovelace));
        //             break;
        //         }
        //     }
        // } else if (currency === 'XRP') {
        //     const { balance } = await fGet(`https://api-${this.location}.tatum.io/v3/${currency}/account/${address}/balance`, {
        //         'x-api-key': this.apiKey,
        //     });
        //     confirmedBalance = Big(balance).times(Big(units.xrp.drop));
        // }

        return {
            result: {
                confirmedBalance: confirmedBalance.toNumber(),
                unconfirmedBalance: undefined,
            },
        };
    }

    async sendTransaction(currency: AvailableCurrencies, hexTransaction: string): Promise<{ result: string }> {
        if (!this.supportedCurrencies.includes(currency)) {
            throw new Error('Currency not supported');
        }

        // const route = currency === 'ADA' || currency === 'XRP' ? currency : currencies[currency].name.toLowerCase();
        const route = currencies[currency].name.toLowerCase();

        const { txId } = await fPost(
            `https://api-${this.location}.tatum.io/v3/${route}/broadcast`,
            {
                txData: hexTransaction,
            },
            {
                'Content-Type': 'application/json',
                'x-api-key': this.apiKey,
            },
        );
        return { result: txId };
    }
}


================================================
FILE: packages/api-providers/src/index.ts
================================================
import NowNodesProvider from './NowNodesProvider';
import SoChainProvider from './SoChainProvider';
import TatumProvider from './TatumProvider';
import GenericProvider from './GenericProvider';
import { ConcreteConstructor } from '@keagate/common';

export type AvailableProviders = 'NowNodes' | 'Tatum';

const idsToProviders: Record<AvailableProviders, ConcreteConstructor<typeof GenericProvider>> = {
    Tatum: TatumProvider,
    NowNodes: NowNodesProvider,
};

export default idsToProviders;
export { NowNodesProvider, SoChainProvider, TatumProvider, GenericProvider };


================================================
FILE: packages/api-providers/src/units.ts
================================================
// https://github.com/markpenovich/cryptocurrency-unit-convert/blob/master/Units.json

const units = {
    btc: {
        satoshi: '0.00000001',
        bit: '0.000001',
        ubtc: '0.000001',
        mbtc: '0.001',
        btc: '1',
    },

    bch: {
        satoshi: '0.00000001',
        bit: '0.000001',
        ubch: '0.000001',
        mbch: '0.001',
        bch: '1',
    },

    eth: {
        wei: '0.000000000000000001',
        kwei: '0.000000000000001',
        mwei: '0.000000000001',
        gwei: '0.000000001',
        finney: '0.001',
        eth: '1',
    },

    xrp: {
        drop: '0.000001',
        xrp: '1',
    },

    ltc: {
        litoshi: '0.00000001',
        photon: '0.000001',
        lite: '0.001',
        ltc: '1',
    },

    dash: {
        duff: '0.00000001',
        dash: '1',
    },

    zec: {
        zatoshi: '0.00000001',
        zec: '1',
    },
    ada: {
        lovelace: '0.000001',
        ada: '1',
    },
};

export default units;


================================================
FILE: packages/api-providers/tsconfig.package.json
================================================
{
	"extends": "../../tsconfig.base.json",
	"compilerOptions": {
		"outDir": "./build",
		"rootDir": "./src",
		"composite": true
	},
	"include": [
		"src/**/*.ts",
		"src/**/*.js",
		"src/**/*.tsx",
		"src/**/*.jsx"
	],
	"exclude": [
		"test",
		"build"
	],
	"references": []
}

================================================
FILE: packages/backend/package.json
================================================
{
  "name": "@keagate/backend",
  "version": "1.0.0",
  "description": "\"# keagate\"",
  "main": "build/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "prebuild": "shx rm -f tsconfig.package.tsbuildinfo",
    "build": "tsc -p ./tsconfig.package.json",
    "dev": "ts-node-dev --project ./tsconfig.package.json --respawn src/index.ts",
    "prepack": "tsc --project tsconfig.build.json",
    "clean": "shx rm -rf build pnpm-lock.yaml node_modules tsconfig.package.tsbuildinfo"
  },
  "dependencies": {
    "@blockfrost/blockfrost-js": "^4.1.0",
    "@fastify/static": "^7.0.1",
    "@fastify/swagger": "^8.14.0",
    "@fastify/swagger-ui": "^3.0.0",
    "@keagate/api-providers": "workspace:^1.0.0",
    "@keagate/common": "workspace:^1.0.0",
    "@sinclair/typebox": "^0.24.7",
    "@solana/web3.js": "^1.44.0",
    "big.js": "^6.2.0",
    "bip32": "^3.0.1",
    "bip39": "^3.0.4",
    "bitcore-lib-ltc": "^8.25.30",
    "bottleneck": "^2.19.5",
    "bs58": "^5.0.0",
    "coinlib-port": "github:dilan-dio4/coinlib-port",
    "config": "^3.3.7",
    "cross-fetch": "^3.1.5",
    "dayjs": "^1.11.3",
    "ed25519-hd-key": "^1.2.0",
    "ethers": "^5.6.9",
    "fastify": "^4.26.0",
    "fastify-plugin": "^4.5.1",
    "find-up": "^6.3.0",
    "lodash.sample": "^4.2.1",
    "mongodb": "^4.7.0",
    "multicoin-address-validator": "^0.5.10",
    "tiny-secp256k1": "^2.2.1",
    "tweetnacl": "^1.0.3"
  },
  "devDependencies": {
    "@types/config": "^0.0.41",
    "@types/lodash.sample": "^4.2.7",
    "@types/node": "^18.0.1",
    "typescript": "^5.4.2"
  }
}


================================================
FILE: packages/backend/src/activityLoop.ts
================================================
import context from './context';
import GenericTransactionalWallet from './transactionalWallets/GenericTransactionalWallet';
import dayjs, { Dayjs } from 'dayjs';
import { PaymentStatusType, availableNativeCurrencies } from '@keagate/common';
import { delay } from './utils';
import config from './config';
import logger from './logger';

class ActivityLoop {
    private needsToStop = false;
    private lastBatchStart: Dayjs;

    public start() {
        this.startBatch();
    }

    public stop() {
        this.needsToStop = true;
    }

    private async runTxsCohort(cohort: GenericTransactionalWallet[]) {
        for (const aTrx of cohort) {
            try {
                await this.checkSingleTransaction(aTrx);
            } catch (e) {
                logger.log('Promise failed', e, aTrx);
            }
        }
    }

    private async startBatch() {
        if (this.needsToStop) {
            this.needsToStop = false;
            return;
        }

        this.lastBatchStart = dayjs();

        const txsByCohort: Record<typeof availableNativeCurrencies[number] | 'coinlib', GenericTransactionalWallet[]> = {
            coinlib: [],
            MATIC: [],
            SOL: [],
        };

        for (const aTrx of Object.values(context.activePayments)) {
            const { type, currency } = aTrx.getDetails();
            if (type === 'coinlib') {
                txsByCohort[type].push(aTrx);
            } else {
                txsByCohort[currency].push(aTrx);
            }
        }

        await Promise.all(Object.values(txsByCohort).map((cohort) => this.runTxsCohort(cohort)));

        const howLongTheBatchTook = dayjs().diff(this.lastBatchStart, 'millisecond');
        if (howLongTheBatchTook < config.getTyped('TRANSACTION_MIN_REFRESH_TIME')) {
            logger.log('Waiting for min refresh time');
            await delay(config.getTyped('TRANSACTION_MIN_REFRESH_TIME') - howLongTheBatchTook);
        }
        this.startBatch();
    }

    private checkSingleTransaction(trx: GenericTransactionalWallet): Promise<PaymentStatusType> {
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                reject(new Error(`Promise timed out after 60s`));
            }, 60000);
            const runner = async () => {
                let _status: PaymentStatusType;
                try {
                    await trx.checkTransaction((status) => (_status = status));
                } catch (error) {
                    _status = undefined;
                }
                if (_status) {
                    clearTimeout(timer);
                    resolve(_status);
                } else {
                    await delay(config.getTyped('BLOCKBOOK_RETRY_DELAY'));
                    runner();
                }
            };
            logger.log('Checking transaction: ', trx.getDetails().id);
            runner();
        });
    }
}

export default new ActivityLoop();


================================================
FILE: packages/backend/src/adminWallets/GenericAdminWallet.ts
================================================
import { availableCoinlibCurrencies, AvailableCurrencies } from '@keagate/common';
import WAValidator from 'multicoin-address-validator';

export default abstract class GenericAdminWallet {
    public currency: AvailableCurrencies;
    protected privateKey: string;

    constructor(constructor: CoinlibAdminConstructor | NativeAdminConstructor) {
        this.setFromObject(constructor);
    }

    public abstract getBalance(): Promise<{ result: { confirmedBalance: number; unconfirmedBalance?: number } }>;
    public abstract sendTransaction(destination: string, amount: number): Promise<{ result: string }>;
    // confirmTransaction
    public isValidAddress(address: string): boolean {
        return WAValidator.validate(address, this.currency);
    }

    protected setFromObject(update: CoinlibAdminConstructor | NativeAdminConstructor) {
        for (const [key, val] of Object.entries(update)) {
            this[key] = val;
        }
    }
}

interface RootAdminConstructor {
    privateKey: string;
}

export interface CoinlibAdminConstructor extends RootAdminConstructor {
    currency: typeof availableCoinlibCurrencies[number];
}

export interface NativeAdminConstructor extends RootAdminConstructor {
    publicKey: string; // TODO: don't need public
}


================================================
FILE: packages/backend/src/adminWallets/coinlib/AdminCoinlibWrapper.ts
================================================
import GenericAdminWallet, { CoinlibAdminConstructor } from '../GenericAdminWallet';
import { AnyPayments, CoinPayments, NetworkType, BaseUnsignedTransaction, BaseSignedTransaction, BaseBroadcastResult, BalanceResult } from 'coinlib-port';
import config from '../../config';
import { delay, requestRetry, deadLogger } from '../../utils';

export default class AdminCoinlibWrapper extends GenericAdminWallet {
    private coinlibMask: AnyPayments<any>;
    private _initialized = false;

    constructor(constructor: CoinlibAdminConstructor) {
        super(constructor);
        this.coinlibMask = CoinPayments.getFactory(this.currency as any).newPayments({
            network: config.getTyped('IS_DEV') ? NetworkType.Testnet : NetworkType.Mainnet,
            addressType: 'p2pkh',
            keyPairs: [this.privateKey],
            /** logger: deadLogger, */
        } as any);

        this.coinlibMask.init().then((_) => (this._initialized = true));
    }

    public async sendTransaction(destination: string, amount: number): Promise<{ result: string }> {
        while (!this._initialized) {
            await delay(1000);
        }

        const createTx = await requestRetry<BaseUnsignedTransaction>(() => this.coinlibMask.createTransaction(0, destination, '' + amount));
        const signedTx = await requestRetry<BaseSignedTransaction>(() => this.coinlibMask.signTransaction(createTx));
        const { id: txHash } = await requestRetry<BaseBroadcastResult>(() => this.coinlibMask.broadcastTransaction(signedTx));
        return { result: txHash };
    }

    public async getBalance(): Promise<{ result: { confirmedBalance: number; unconfirmedBalance?: number } }> {
        while (!this._initialized) {
            await delay(1000);
        }

        const { confirmedBalance, unconfirmedBalance } = await requestRetry<BalanceResult>(() => this.coinlibMask.getBalance(0));
        return {
            result: {
                confirmedBalance: +confirmedBalance,
                unconfirmedBalance: +unconfirmedBalance,
            },
        };
    }
}


================================================
FILE: packages/backend/src/adminWallets/native/GenericNativeAdminWallet.ts
================================================
import GenericAdminWallet from '../GenericAdminWallet';

export default abstract class GenericNativeAdminWallet extends GenericAdminWallet {
    public publicKey: string;
}


================================================
FILE: packages/backend/src/adminWallets/native/Polygon/index.ts
================================================
import GenericNativeAdminWallet from '../GenericNativeAdminWallet';
import { ethers } from 'ethers';
import { NativeAdminConstructor } from '../../GenericAdminWallet';
import { availableNativeCurrencies } from '@keagate/common';
import config from '../../../config';
import Big from 'big.js';
import limiters from '../../../limiters';

export default class AdminPolygon extends GenericNativeAdminWallet {
    private provider: ethers.providers.JsonRpcProvider;
    private wallet: ethers.Wallet;
    public currency: typeof availableNativeCurrencies[number] = 'MATIC';

    constructor(constructor: NativeAdminConstructor) {
        super(constructor);
        this.provider = new ethers.providers.JsonRpcProvider(this.getRandomRPC());
        this.wallet = new ethers.Wallet(this.privateKey, this.provider);
    }

    private getRandomRPC() {
        if (config.getTyped('IS_DEV')) {
            return 'https://rpc.ankr.com/polygon_mumbai';
        } else {
            return 'https://rpc.ankr.com/polygon';
        }
    }

    async getBalance() {
        const balance = await limiters[this.currency].schedule(() => this.wallet.getBalance());
        // https://github.com/ethjs/ethjs-unit/blob/35d870eae1c32c652da88837a71e252a63a83ebb/src/index.js#L38
        const confirmedBalance = Big(balance.toString()).div('1000000000000000000').toNumber();
        return {
            result: {
                confirmedBalance,
                unconfirmedBalance: null,
            },
        };
    }

    async sendTransaction(destination: string, amount: number) {
        if (!this.isValidAddress(destination)) {
            throw new Error('Invalid destination address');
        }
        const gasPrice = await limiters[this.currency].schedule(() => this.provider.getGasPrice());

        // https://docs.ethers.io/v4/cookbook-accounts.html
        const gasLimit = 21000;
        const value = ethers.utils.parseEther('' + amount).sub(gasPrice.mul(gasLimit));
        // https://docs.ethers.io/v5/api/providers/provider/#Provider-sendTransaction
        const tx = {
            to: destination,
            value,
            gasPrice,
            gasLimit,
        };
        const txObj = await limiters[this.currency].schedule(() => this.wallet.sendTransaction(tx));
        return { result: txObj.hash };
    }
}


================================================
FILE: packages/backend/src/adminWallets/native/Solana/HdWallet.ts
================================================
// https://github.com/p2p-org/p2p-wallet-web/blob/2d894ae5893b8566760c8a52050fde7e918d3139/packages/core/src/contexts/seed/utils/hd_wallet.ts
import * as ed25519 from 'ed25519-hd-key';
import nacl from 'tweetnacl';
import { bip32 } from '../../../utils';
import * as bip39 from 'bip39';
import { PublicKey } from '@solana/web3.js';
import config from '../../../config';

const DERIVATION_PATH = {
    Deprecated: 'deprecated',
    Bip44: 'bip44',
    Bip44Change: 'bip44Change',
};

type ValueOf<T> = T[keyof T];

const deriveSeed = (seed: string, walletIndex: number, derivationPath: ValueOf<typeof DERIVATION_PATH>): Buffer | undefined => {
    switch (derivationPath) {
        case DERIVATION_PATH.Deprecated: {
            const path = `m/501'/${walletIndex}'/0/0`;
            return bip32.fromSeed(Buffer.from(seed, 'hex')).derivePath(path).privateKey;
        }
        case DERIVATION_PATH.Bip44: {
            const path = `m/44'/501'/${walletIndex}'`;
            return ed25519.derivePath(path, seed).key;
        }
        case DERIVATION_PATH.Bip44Change: {
            const path = `m/44'/501'/${walletIndex}'/0'`;
            return ed25519.derivePath(path, seed).key;
        }
    }
};

export function getKeyPairFromSeed(seed: string, walletIndex: number, derivationPath: ValueOf<typeof DERIVATION_PATH>) {
    const derivedPrivateKey = deriveSeed(seed, walletIndex, derivationPath);
    if (!derivedPrivateKey) throw new Error('Could not derive secretKey');
    return nacl.sign.keyPair.fromSeed(derivedPrivateKey);
}

// const derivePublicKeyFromSeed = (
//     seed: string,
//     walletIndex: number,
//     derivationPath: ValueOf<typeof DERIVATION_PATH>,
// ) => {
//     return getKeyPairFromSeed(seed, walletIndex, derivationPath).publicKey
// }

// const deriveSecretKeyFromSeed = (
//     seed: string,
//     walletIndex: number,
//     derivationPath: ValueOf<typeof DERIVATION_PATH>,
// ) => {
//     return getKeyPairFromSeed(seed, walletIndex, derivationPath).secretKey
// }

export const generateMnemonicAndSeedAsync = async () => {
    const mnemonic = bip39.generateMnemonic(256);
    const seed = await bip39.mnemonicToSeed(mnemonic);
    return {
        mnemonic,
        seed: Buffer.from(seed).toString('hex'),
    };
};

// const mnemonicToSeed = async (mnemonic: string) => {
//     if (!bip39.validateMnemonic(mnemonic)) {
//         throw new Error('Invalid seed words')
//     }
//     const seed = await bip39.mnemonicToSeed(mnemonic)
//     return Buffer.from(seed).toString('hex')
// }

export default function generateKeypair(walletIndex: number) {
    const seed = config.getTyped('SEED');
    const keypair = getKeyPairFromSeed(seed, walletIndex, 'bip44');
    return {
        publicKey: new PublicKey(keypair.publicKey).toString(),
        secretKey: keypair.secretKey,
    };
}


================================================
FILE: packages/backend/src/adminWallets/native/Solana/index.ts
================================================
import { Connection, clusterApiUrl, PublicKey, Keypair, Transaction, SystemProgram, LAMPORTS_PER_SOL, sendAndConfirmTransaction } from '@solana/web3.js';
import GenericNativeAdminWallet from '../GenericNativeAdminWallet';
import base58 from 'bs58';
import { availableNativeCurrencies } from '@keagate/common';
import config from '../../../config';
import { NativeAdminConstructor } from '../../GenericAdminWallet';
import sample from 'lodash.sample';
import limiters from '../../../limiters';

export default class AdminSolana extends GenericNativeAdminWallet {
    private connection: Connection;
    public currency: typeof availableNativeCurrencies[number] = 'SOL';
    static TRANSFER_FEE_LAMPORTS = 5000;

    constructor(constructor: NativeAdminConstructor) {
        super(constructor);
        this.connection = new Connection(this.getRandomRPC(), 'confirmed');
    }

    private getRandomRPC() {
        if (config.getTyped('IS_DEV')) {
            return sample([clusterApiUrl('devnet'), 'https://rpc.ankr.com/solana_devnet']);
        } else {
            return sample([clusterApiUrl('mainnet-beta'), 'https://api.mainnet-beta.solana.com']);
        }
    }

    async getBalance() {
        const balance = await limiters[this.currency].schedule(() => this.connection.getBalance(new PublicKey(this.publicKey), 'confirmed'));

        return {
            result: {
                confirmedBalance: balance / LAMPORTS_PER_SOL,
                unconfirmedBalance: undefined,
            },
        };
    }

    async sendTransaction(destination: string, amount: number) {
        if (!this.isValidAddress(destination)) {
            throw new Error('Invalid destination address');
        }

        const latestBlockhash = await limiters[this.currency].schedule(() => this.connection.getLatestBlockhash('confirmed'));

        const adminKeypair = Keypair.fromSecretKey(base58.decode(this.privateKey));

        const transaction = new Transaction().add(
            SystemProgram.transfer({
                fromPubkey: adminKeypair.publicKey,
                toPubkey: new PublicKey(destination),
                lamports: Math.round(amount * LAMPORTS_PER_SOL) - AdminSolana.TRANSFER_FEE_LAMPORTS,
            }),
        );

        transaction.recentBlockhash = latestBlockhash.blockhash;
        transaction.feePayer = adminKeypair.publicKey;

        try {
            const signature = await limiters[this.currency].schedule(() => sendAndConfirmTransaction(this.connection, transaction, [adminKeypair]));
            return { result: signature };
        } catch (error) {
            throw new Error(error);
        }
    }
}


================================================
FILE: packages/backend/src/config.ts
================================================
// https://itnext.io/node-config-made-type-safe-5be0a08ad5ba
import path from 'path';
// TODO: Suppress NODE_APP_INSTANCE warning with PM2 + node-config
process.env['NODE_CONFIG_DIR'] = path.join(__dirname, '..', '..', '..', 'config/'); // Must be in this order
import config from 'config';
import { MyConfig } from '@keagate/common';

const getTyped: <T extends keyof MyConfig>(key: T) => MyConfig[T] = <T extends keyof MyConfig>(key: T) => config.get(key);
const has: (setting: string) => boolean = (setting: string) => config.has(setting);

const obj = {
    getTyped,
    has,
};

export default obj;


================================================
FILE: packages/backend/src/context.ts
================================================
import { availableCoinlibCurrencies, AvailableCurrencies, availableNativeCurrencies, ConcreteConstructor, currencies, arrayIncludes } from '@keagate/common';
import { AnyPayments } from 'coinlib-port';
import { WithId } from 'mongodb';
import GenericAdminWallet from './adminWallets/GenericAdminWallet';
import config from './config';
import { getExistingPayments } from './mongo';
import TransactionalCoinlibWrapper from './transactionalWallets/coinlib/TransactionalCoinlibWrapper';
import GenericTransactionalWallet from './transactionalWallets/GenericTransactionalWallet';
import GenericNativeTransactionalWallet from './transactionalWallets/native/GenericNativeTransactionalWallet';
import { CoinlibPayment, NativePayment } from './types';
import { getCoinlibCurrencyToClient, getNativeCurrencyToClient } from './currenciesToClients';

class KeagateContext {
    public enabledNativeCurrencies: typeof availableNativeCurrencies[number][] = [];
    public enabledCoinlibCurrencies: typeof availableCoinlibCurrencies[number][] = [];
    public coinlibCurrencyToClient: Record<string, AnyPayments<any>> = {};
    public nativeCurrencyToClient: Record<
        typeof availableNativeCurrencies[number],
        {
            Admin: ConcreteConstructor<typeof GenericAdminWallet>;
            Transactional: ConcreteConstructor<typeof GenericNativeTransactionalWallet>;
        }
    >;

    public activePayments: Record<string, GenericTransactionalWallet> = {};

    public async init() {
        // Preserve order
        this.initEnabledCurrencies();
        this.nativeCurrencyToClient = getNativeCurrencyToClient();
        this.coinlibCurrencyToClient = await getCoinlibCurrencyToClient();
        await this.initActivePayments();
    }

    private initEnabledCurrencies() {
        for (const currency of Object.keys(currencies)) {
            const typedCurrency = currency as any;
            if (!!config.has(currency) && !!config.getTyped(typedCurrency).ADMIN_PUBLIC_KEY) {
                if (availableNativeCurrencies.includes(typedCurrency)) {
                    this.enabledNativeCurrencies.push(typedCurrency);
                } else if (availableCoinlibCurrencies.includes(typedCurrency)) {
                    this.enabledCoinlibCurrencies.push(typedCurrency);
                }
            }
        }
    }

    public async initActivePayments() {
        // Collect all existing native payments in mongo and initalize them in the activePayments maps
        const _activeNativePayments = (await getExistingPayments()) as WithId<NativePayment | CoinlibPayment>[];
        this.activePayments = {};
        for (const _currActivePayment of _activeNativePayments) {
            const currTxCurrency = _currActivePayment.currency as AvailableCurrencies;

            if (_currActivePayment.type === 'native') {
                if (arrayIncludes(this.enabledNativeCurrencies, currTxCurrency)) {
                    this.activePayments[_currActivePayment._id.toString()] = new this.nativeCurrencyToClient[currTxCurrency].Transactional().fromManual(
                        {
                            ..._currActivePayment,
                            id: _currActivePayment._id.toString(),
                        },
                        {
                            onDie: (id) => delete this.activePayments[id],
                            adminWalletClass: this.nativeCurrencyToClient[currTxCurrency].Admin,
                        },
                    );
                } else {
                    console.error(`No transactional wallet found/enabled for currency ${_currActivePayment.currency}: ${_currActivePayment._id}`);
                    continue;
                }
            } else if (_currActivePayment.type === 'coinlib') {
                if (arrayIncludes(this.enabledCoinlibCurrencies, currTxCurrency)) {
                    this.activePayments[_currActivePayment._id.toString()] = new TransactionalCoinlibWrapper().fromManual(
                        {
                            ..._currActivePayment,
                            id: _currActivePayment._id.toString(),
                        },
                        {
                            onDie: (id) => delete this.activePayments[id],
                            walletIndex: _currActivePayment.walletIndex,
                            currency: currTxCurrency,
                            coinlibPayment: this.coinlibCurrencyToClient[currTxCurrency],
                        },
                    );
                } else {
                    console.error(`No transactional wallet found/enabled for currency ${_currActivePayment.currency}: ${_currActivePayment._id}`);
                    continue;
                }
            }
        }
    }
}

export default new KeagateContext();


================================================
FILE: packages/backend/src/currenciesToClients.ts
================================================
import { availableCoinlibCurrencies, availableNativeCurrencies, ConcreteConstructor } from '@keagate/common';
import { AnyPayments, CoinPayments, NetworkType, SUPPORTED_NETWORK_SYMBOLS } from 'coinlib-port';
import config from './config';
import GenericAdminWallet from './adminWallets/GenericAdminWallet';
import AdminPolygon from './adminWallets/native/Polygon';
import AdminSolana from './adminWallets/native/Solana';
import GenericNativeTransactionalWallet from './transactionalWallets/native/GenericNativeTransactionalWallet';
import TransactionalPolygon from './transactionalWallets/native/Polygon';
import TransactionalSolana from './transactionalWallets/native/Solana';
import { deadLogger } from './utils';

export const getNativeCurrencyToClient = (): Record<
    typeof availableNativeCurrencies[number],
    {
        Admin: ConcreteConstructor<typeof GenericAdminWallet>;
        Transactional: ConcreteConstructor<typeof GenericNativeTransactionalWallet>;
    }
> => ({
    SOL: {
        Admin: AdminSolana,
        Transactional: TransactionalSolana,
    },
    MATIC: {
        Admin: AdminPolygon,
        Transactional: TransactionalPolygon,
    },
});

export async function getCoinlibCurrencyToClient(): Promise<Record<typeof availableCoinlibCurrencies[number], AnyPayments<any>>> {
    const coinPayments = new CoinPayments({ seed: config.getTyped('SEED'), network: NetworkType.Mainnet /** logger: deadLogger */ });
    const coinlibCurrencyToClient: Partial<Record<typeof availableCoinlibCurrencies[number], AnyPayments<any>>> = {};
    for (const _currency of SUPPORTED_NETWORK_SYMBOLS) {
        const currClient = coinPayments.forNetwork(_currency);
        await currClient.init();
        coinlibCurrencyToClient[_currency] = currClient;
    }
    return coinlibCurrencyToClient as Record<typeof availableCoinlibCurrencies[number], AnyPayments<any>>;
}


================================================
FILE: packages/backend/src/devServer.ts
================================================
import fastify, { FastifyInstance } from 'fastify';
import crypto from 'crypto';
import config from './config';

interface IDevServer {
    server: FastifyInstance;
    name: string;
    port: number;
}

function createIPNCallbackServer(): IDevServer {
    /**
     * For receiving IPN callbacks locally. Never use in production. Ideally, use
     * something like lambda or Azure functions.
     * More information here: https://github.com/dilan-dio4/coinlib-port#instant-payment-notifications
     */
    const ipnConsumer = fastify({
        trustProxy: true,
    });
    ipnConsumer.post<{ Body: Record<string, any> }>('/ipnCallback', (request, reply) => {
        const send = (status: string) => {
            console.log(`[IPN CALLBACK DEV]: ${status}`);
            reply.send(status);
        };
        if (request.headers['x-keagate-sig']) {
            const hmac = crypto.createHmac('sha512', config.getTyped('IPN_HMAC_SECRET'));
            hmac.update(JSON.stringify(request.body, Object.keys(request.body).sort()));
            const signature = hmac.digest('hex');

            if (signature === request.headers['x-keagate-sig']) {
                send(
                    'x-keagate-sig matches calculated signature. Can authenticate origin and validate message integrity.\n\n' +
                        JSON.stringify(request.body, null, 2),
                );
            } else {
                send(
                    `x-keagate-sig header (${request.headers['x-keagate-sig']}) does not match calculated signature (${signature}). Cannot authenticate origin and validate message integrity.` +
                        JSON.stringify(request.body, null, 2),
                );
            }
        } else {
            send('No x-keagate-sig found on POST request. Cannot authenticate origin and validate message integrity.');
        }
    });

    return {
        server: ipnConsumer,
        name: 'IPN CALLBACK DEV',
        port: 8082,
    };
}

export default async function devServer(server: FastifyInstance) {
    const devServers: IDevServer[] = [createIPNCallbackServer()];
    for (const currServer of devServers) {
        await currServer.server.ready();
        currServer.server.listen({ port: currServer.port }, (err, address) => {
            if (err) {
                console.error(err);
                process.exit(1);
            }
            console.log(`[${currServer.name}]: dev server listening at ${address}`);
        });
    }

    /**
     * For testing redirects from the built-in invoice interface. This should basically
     * be a website that utilizes the `status` and `invoice_id` query parameters
     * that Keagate automatically appends to the passed in URL.
     */
    server.get('/dev-callback-site', (request, reply) => {
        reply.header('Content-Type', 'text/html; charset=UTF-8');
        reply.send(`
        <!DOCTYPE html>
<html>

<body>
    <h1 style="background-color: red; color: white;">Keagate test site</h1>
    <h2 style="font-weight: 400;"><b>NEVER</b> actually call administrative routes or store your <em>keagate-api-key</em> directly with a user's browser!</h2>
    <h2 style="font-weight: 400;">Instead, call an API or Lambda from your site and invokes Keagate from there. &#128515; cheers!</h2>

    <hr />

    <p><b>Data read in URL params:</b></p>
    <ol id="url-params-data"></ol>

    <p><b>Data read from Keagate based on <em>invoice_id</em>:</b></p>
    <ol id="keagate-data"></ol>

    <p id="error-message" style="color: red;"></p>
</body>
<script type="text/javascript">
    window.addEventListener('load', function () {
        const params = new URLSearchParams(document.location.search)

        let newElements = '';
        params.forEach(function (value, key) {
            newElements += '<li><b>' + key + '</b>: ' + value + '</li>'
        })
        document.getElementById('url-params-data').innerHTML = newElements;

        if (!params.get('invoice_id')) {
            document.getElementById('error-message').innerText = "No invoice_id parameter found"
        } else {
            fetch('http://localhost:8081/getPaymentStatus?id=' + params.get('invoice_id'), {
                headers: {
                    "keagate-api-key": "${config.getTyped('KEAGATE_API_KEY')}"
                }
            })
                .then(res => res.json())
                .then(res => {
                    let newKeagateElements = '';
                    for (const [key, value] of Object.entries(res)) {
                        newKeagateElements += '<li><b>' + key + '</b>: ' + value + '</li>'
                    }
                    document.getElementById('keagate-data').innerHTML = newKeagateElements;

                })
                .catch(err => document.getElementById('error-message').innerText = JSON.stringify(err))
        }
    })
</script>

</html>
        `);
    });
}


================================================
FILE: packages/backend/src/index.ts
================================================
import config from './config';
import fastify from 'fastify';
import { currencies, availableCoinlibCurrencies, availableNativeCurrencies } from '@keagate/common';
import GenericAdminWallet from './adminWallets/GenericAdminWallet';
import auth from './middlewares/auth';
import createPaymentRoute from './routes/createPayment';
import createActivePaymentsRoute from './routes/activePayments';
import createPaymentStatusRoute from './routes/paymentStatus';
import createInvoiceClientRoute from './routes/invoiceClient';
import createSwaggerRoute from './routes/swagger';
import createInvoiceStatusRoute from './routes/invoiceStatus';
import createPaymentsByExtraIdRoute from './routes/paymentsByExtraId';
import context from './context';
import activityLoop from './activityLoop';
import AdminCoinlibWrapper from './adminWallets/coinlib/AdminCoinlibWrapper';
import devServer from './devServer';
import logger from './logger';

const server = fastify({
    trustProxy: true,
    ajv: {
        customOptions: {
            strict: 'log',
            keywords: ['kind', 'modifier'],
        },
    },
});

/**
 * Native = currency processed by a wallet built into Keagate
 * Coinlib = currency processed by the port of coinlib
 */

async function main() {
    await context.init();
    server.register(createSwaggerRoute);
    // Initialize the admin wallet routes for native currencies
    for (const _currency of context.enabledNativeCurrencies) {
        const coinName = currencies[_currency].name as typeof availableNativeCurrencies[number];
        const publicKey: string = config.getTyped(_currency).ADMIN_PUBLIC_KEY;
        const privateKey: string = config.getTyped(_currency).ADMIN_PRIVATE_KEY;

        if (!publicKey || !privateKey) {
            console.error(`No admin public key and private key found for currency ${_currency}`);
            continue;
        }

        let currentClient: GenericAdminWallet;
        if (context.nativeCurrencyToClient[_currency]) {
            currentClient = new context.nativeCurrencyToClient[_currency].Admin({
                publicKey,
                privateKey,
            });
        } else {
            console.error(`No admin wallet found for currency ${_currency}`);
            continue;
        }
        // Get the balance of and send a transaction from the admin wallet
        server.get(`/get${coinName}Balance`, { preHandler: auth }, (request, reply) => currentClient.getBalance());
        server.post<{ Body: Record<string, any> }>(`/send${coinName}Transaction`, { preHandler: auth }, (request, reply) =>
            currentClient.sendTransaction(request.body.destination, request.body.amount),
        );
    }

    // Do the same for coinlib currencies
    for (const _currency of context.enabledCoinlibCurrencies) {
        const coinName = currencies[_currency].name as typeof availableCoinlibCurrencies[number];

        const publicKey: string = config.getTyped(_currency).ADMIN_PUBLIC_KEY;
        const privateKey: string = config.getTyped(_currency).ADMIN_PRIVATE_KEY;

        if (!publicKey || !privateKey) {
            console.error(`No admin public key and private key found for currency ${_currency}`);
            continue;
        }

        const currentClient = new AdminCoinlibWrapper({
            currency: _currency,
            privateKey: privateKey,
        });

        server.get(`/get${coinName}Balance`, { preHandler: auth }, (request, reply) => currentClient.getBalance());
        server.post<{ Body: Record<string, any> }>(`/send${coinName}Transaction`, { preHandler: auth }, (request, reply) =>
            currentClient.sendTransaction(request.body.destination, request.body.amount),
        );
    }

    // Create other routes for API and invoice client
    server.register(createInvoiceClientRoute);
    server.register(createInvoiceStatusRoute);
    server.register(createPaymentRoute);
    server.register(createActivePaymentsRoute);
    server.register(createPaymentStatusRoute);
    server.register(createPaymentsByExtraIdRoute);

    // Start the processing intervals
    activityLoop.start();

    if (config.getTyped('IS_DEV') && config.has('IPN_HMAC_SECRET')) {
        server.register(devServer);
    }

    await server.ready();
    server.swagger();
    server.listen({ port: config.getTyped('PORT') }, (err, address) => {
        if (err) {
            console.error(err);
            process.exit(1);
        }
        logger.log(`Keagate backend server listening at ${address}`);
    });
}

main();


================================================
FILE: packages/backend/src/limiters.ts
================================================
import { availableNativeCurrencies } from '@keagate/common';
import Bottleneck from 'bottleneck';

interface ILimiterDetails {
    rateLimit: number;
    maxRetries: number;
    maxConcurrent: number;
}

const limiterDetails: Record<typeof availableNativeCurrencies[number], ILimiterDetails> = {
    SOL: {
        maxConcurrent: 1,
        maxRetries: 5,
        rateLimit: 500,
    },
    MATIC: {
        maxConcurrent: 1,
        maxRetries: 5,
        rateLimit: 500,
    },
};

function generateLimiter(ILimiterDetails: ILimiterDetails) {
    const RATE_LIMIT = ILimiterDetails.rateLimit;

    const limiter = new Bottleneck({
        minTime: RATE_LIMIT,
        maxConcurrent: ILimiterDetails.maxConcurrent,
    });

    limiter.on('failed', async (error, jobInfo) => {
        if (jobInfo.retryCount < ILimiterDetails.maxRetries) {
            return RATE_LIMIT + jobInfo.retryCount * 100;
        }
    });

    return limiter;
}

const limiters: Record<typeof availableNativeCurrencies[number], Bottleneck> = {
    MATIC: generateLimiter(limiterDetails.MATIC),
    SOL: generateLimiter(limiterDetails.SOL),
};

export default limiters;


================================================
FILE: packages/backend/src/logger.ts
================================================
import config from './config';

class DelegateLogger {
    log = (...args: any[]) => console.log('[KEAGATE LOG]: ', ...args);
    debug = (...args: any[]) => config.has('IS_DEV') && config.getTyped('IS_DEV') && console.log('[KEAGATE DEBUG]: ', ...args);
    success = (text = 'Complete') => console.log('\x1b[1;32m \u2714 ' + text + '\x1b[0m');
}

const logger = new DelegateLogger();

export default logger;


================================================
FILE: packages/backend/src/middlewares/auth.ts
================================================
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from 'fastify';
import config from '../config';

let IP_WHITELIST: Set<string> = undefined;

if (config.getTyped('IP_WHITELIST').length > 0) {
    IP_WHITELIST = new Set(config.getTyped('IP_WHITELIST'));
    IP_WHITELIST.add('127.0.0.1');
}

const auth = (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => {
    if (request.headers['keagate-api-key'] === config.getTyped('KEAGATE_API_KEY')) {
        if (IP_WHITELIST !== undefined) {
            if (IP_WHITELIST.has(request.ip)) {
                done();
            }
        } else {
            done();
        }
    }
};

export default auth;


================================================
FILE: packages/backend/src/mongo/generator.ts
================================================
import { MongoClient } from 'mongodb';
import config from '../config';

let clientInstance: MongoClient;

export default async function generator() {
    if (!clientInstance) {
        // Default options: https://github.com/mongodb/node-mongodb-native/blob/ee414476aa839e364bce6b26ab47859be1b99307/src/connection_string.ts#L825
        // https://stackoverflow.com/a/56438581
        const _client = new MongoClient(config.getTyped('MONGO_CONNECTION_STRING'), {
            keepAlive: true,
            socketTimeoutMS: 2000000,
        });
        await _client.connect();
        _client.on('topologyClosed', () => (clientInstance = undefined));
        clientInstance = _client;
    }

    return {
        db: clientInstance.db(config.getTyped('MONGO_KEAGATE_DB')),
        client: clientInstance,
    };
}


================================================
FILE: packages/backend/src/mongo/index.ts
================================================
import { Db } from 'mongodb';
import mongoGenerator from './generator';

const _load = (db: Db, collection: 'payments') =>
    db
        .collection(collection)
        .find({ status: { $nin: ['FINISHED', 'EXPIRED', 'FAILED', 'CONFIRMED'] } })
        .toArray();

export async function getExistingPayments() {
    const { db } = await mongoGenerator();
    return await _load(db, 'payments');
}


================================================
FILE: packages/backend/src/routes/activePayments.ts
================================================
import { Static, Type } from '@sinclair/typebox';
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
import { MongoPayment, ForRequest } from '../types';
import auth from '../middlewares/auth';
import context from '../context';
import { MongoTypeForRequest, cleanDetails, AdminRouteHeaders } from './types';

const ActivePaymentsResponse = Type.Array(MongoTypeForRequest, { description: 'Successful response of array of all active payments' });

const opts: RouteShorthandOptions = {
    schema: {
        response: {
            200: ActivePaymentsResponse,
        },
        headers: AdminRouteHeaders,
        tags: ['Payment'],
        description: 'Fetch an array of all active payments. Payments that have been completed or expired will not appear here.',
        summary: 'Fetch an array of all active payments',
        security: [
            {
                ApiKey: [],
            },
        ],
    },
    preHandler: auth,
};

export default async function createActivePaymentsRoute(server: FastifyInstance) {
    server.get<{ Reply: Static<typeof ActivePaymentsResponse> }>('/activePayments', opts, async (request, reply) => {
        await context.initActivePayments();
        const cleanedTransactions: ForRequest<MongoPayment>[] = Object.values(context.activePayments).map((ele) => cleanDetails(ele.getDetails()));
        reply.status(200).send(cleanedTransactions);
    });
}


================================================
FILE: packages/backend/src/routes/createPayment.ts
================================================
import { Static, Type } from '@sinclair/typebox';
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
import auth from '../middlewares/auth';
import GenericTransactionalWallet from '../transactionalWallets/GenericTransactionalWallet';
import { AvailableCurrencies, arrayIncludes } from '@keagate/common';
import TransactionalCoinlibWrapper from '../transactionalWallets/coinlib/TransactionalCoinlibWrapper';
import { walletIndexGenerator } from '../transactionalWallets/coinlib/trxLimits';
import context from '../context';
import { currencyDusts } from '../transactionalWallets/coinlib/trxLimits';
import { cleanDetails, MongoTypeForRequest, AdminRouteHeaders, ErrorResponse } from './types';
import { IFromNew } from '../types';

const CreatePaymentBody = Type.Pick(MongoTypeForRequest, ['currency', 'amount', 'ipnCallbackUrl', 'invoiceCallbackUrl', 'extraId']);

const opts: RouteShorthandOptions = {
    schema: {
        body: CreatePaymentBody,
        response: {
            200: MongoTypeForRequest,
            300: ErrorResponse,
        },
        headers: AdminRouteHeaders,
        tags: ['Payment'],
        description: 'Create a new payment.',
        summary: 'Create a new payment',
        security: [
            {
                ApiKey: [],
            },
        ],
    },
    preHandler: auth,
};

export default async function createPaymentRoute(server: FastifyInstance) {
    server.post<{ Body: Static<typeof CreatePaymentBody>; Reply: Static<typeof MongoTypeForRequest> | Static<typeof ErrorResponse> }>(
        '/createPayment',
        opts,
        async (request, reply) => {
            const { body } = request;

            const createCurrency = body.currency.toUpperCase() as AvailableCurrencies;
            let transactionalWallet: GenericTransactionalWallet;
            const transactionalWalletNewObj: IFromNew = {
                amount: body.amount,
                invoiceCallbackUrl: body.invoiceCallbackUrl,
                ipnCallbackUrl: body.ipnCallbackUrl,
                extraId: body.extraId,
            };
            if (arrayIncludes(context.enabledNativeCurrencies, createCurrency)) {
                transactionalWallet = await new context.nativeCurrencyToClient[createCurrency].Transactional().fromNew(transactionalWalletNewObj, {
                    onDie: (id) => delete context.activePayments[id],
                    adminWalletClass: context.nativeCurrencyToClient[createCurrency].Admin,
                });
            } else if (arrayIncludes(context.enabledCoinlibCurrencies, createCurrency)) {
                if (createCurrency in currencyDusts && currencyDusts[createCurrency] >= body.amount) {
                    reply
                        .status(300)
                        .send({ error: `Transaction amount is lower than the minimum for ${createCurrency}: ${currencyDusts[createCurrency]} dust` });
                    return;
                }
                transactionalWallet = await new TransactionalCoinlibWrapper().fromNew(transactionalWalletNewObj, {
                    onDie: (id) => delete context.activePayments[id],
                    currency: createCurrency,
                    walletIndex: walletIndexGenerator[createCurrency](),
                    coinlibPayment: context.coinlibCurrencyToClient[createCurrency],
                });
            } else {
                console.error(`No transactional wallet found/enabled for currency ${body.currency}`);
                return;
            }

            context.activePayments[transactionalWallet.getDetails().id] = transactionalWallet;
            reply.status(200).send(cleanDetails(transactionalWallet.getDetails()));
        },
    );
}


================================================
FILE: packages/backend/src/routes/invoiceClient.ts
================================================
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
import fastifyStatic from '@fastify/static';
import path from 'path';
import fastifyPlugin from 'fastify-plugin';
import { Type } from '@sinclair/typebox';

export default fastifyPlugin(async function createInvoiceClientRoute(server: FastifyInstance) {
    server.register(fastifyStatic, {
        root: path.join(__dirname, '..', '..', '..', 'invoice-client', 'dist'),
        prefix: '/static-invoice',
    });

    const opts: RouteShorthandOptions = {
        schema: {
            tags: ['Invoice'],
            description: `Route for Keagate's built-in invoice interface.`,
            summary: `Route for Keagate's built-in invoice interface`,
            params: Type.Object({
                currency: Type.String({
                    description: `Shorthand name of a payment's corresponding currency.`,
                }),
                invoiceId: Type.String({
                    description: `Invoice identifier. This is generated automatically as the internal id of the payment encrypted with aes-256-cbc plus your config's INVOICE_ENC_KEY.`,
                }),
            }),
        },
    };

    server.get('/invoice/:currency/:invoiceId', opts, (request, reply) => {
        reply.sendFile('index.html');
    });
});


================================================
FILE: packages/backend/src/routes/invoiceStatus.ts
================================================
import { Static, Type } from '@sinclair/typebox';
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
import { MongoPayment } from '../types';
import mongoGenerator from '../mongo/generator';
import { ObjectId, WithId } from 'mongodb';
import { decrypt } from '../utils';
import { ErrorResponse, MongoTypeForRequest } from './types';

const InvoiceStatusResponse = Type.Pick(
    MongoTypeForRequest,
    ['publicKey', 'amount', 'amountPaid', 'expiresAt', 'status', 'currency', 'invoiceCallbackUrl', 'memo'],
    {
        description: `Successful response of invoice status.`,
    },
);

const InvoiceStatusQueryString = Type.Object({
    invoiceId: Type.String({
        description: `The encrypted identifier given as the second part of the *invoiceUrl* path. This *invoiceUrl* is found in most of the administrative Keagate routes.`,
    }),
});

const opts: RouteShorthandOptions = {
    schema: {
        response: {
            200: InvoiceStatusResponse,
            300: ErrorResponse,
        },
        querystring: InvoiceStatusQueryString,
        tags: ['Invoice'],
        description: `Retrieve the status and associated data of an invoice. This is essentially the same as \`/paymentStatus\` except it doesn't 
        return any sensitive payment information and can be safely invoked from clients' machines to build custom payment interfaces`,
        summary: 'Retrieve the status and associated data of an invoice',
    },
};

export default async function createInvoiceStatusRoute(server: FastifyInstance) {
    server.get<{ Reply: Static<typeof InvoiceStatusResponse> | Static<typeof ErrorResponse>; Querystring: Static<typeof InvoiceStatusQueryString> }>(
        '/getInvoiceStatus',
        opts,
        async (request, reply) => {
            const invoiceId = request.query.invoiceId;
            const mongoId = decrypt(invoiceId);
            const { db } = await mongoGenerator();
            const selectedPayment = (await db.collection('payments').findOne({ _id: new ObjectId(mongoId) })) as WithId<MongoPayment>;
            if (!selectedPayment) {
                reply.status(300).send({ error: `No transaction found with given id` });
                return;
            }

            reply.status(200).send({
                publicKey: selectedPayment.publicKey,
                amount: selectedPayment.amount,
                expiresAt: selectedPayment.expiresAt.toISOString(),
                status: selectedPayment.status,
                amountPaid: selectedPayment.amountPaid,
                currency: selectedPayment.currency,
                invoiceCallbackUrl: selectedPayment.invoiceCallbackUrl,
                memo: 'memo' in selectedPayment ? selectedPayment.memo : undefined,
            });
        },
    );
}


================================================
FILE: packages/backend/src/routes/paymentStatus.ts
================================================
import { Static, Type } from '@sinclair/typebox';
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
import auth from '../middlewares/auth';
import mongoGenerator from '../mongo/generator';
import { ObjectId, WithId } from 'mongodb';
import { MongoTypeForRequest, cleanDetails, AdminRouteHeaders, ErrorResponse } from './types';
import { decrypt } from '../utils';

const PaymentStatusQueryString = Type.Object({
    id: Type.String({
        description: `The database id or invoice id of an existing payment`,
    }),
});

const opts: RouteShorthandOptions = {
    schema: {
        response: {
            300: ErrorResponse,
            200: MongoTypeForRequest,
        },
        querystring: PaymentStatusQueryString,
        headers: AdminRouteHeaders,
        tags: ['Payment'],
        description: 'Retrieve the status and associated data of a payment.',
        summary: 'Retrieve the status and associated data of a payment',
        security: [
            {
                ApiKey: [],
            },
        ],
    },
    preHandler: auth,
};

export default async function createPaymentStatusRoute(server: FastifyInstance) {
    server.get<{
        Reply: Static<typeof MongoTypeForRequest> | Static<typeof ErrorResponse>;
        Querystring: Static<typeof PaymentStatusQueryString>;
    }>('/getPaymentStatus', opts, async (request, reply) => {
        const providedId = request.query.id;
        const databaseId = providedId.length === 64 ? decrypt(providedId) : providedId;
        const { db } = await mongoGenerator();
        const selectedPayment = (await db.collection('payments').findOne({ _id: new ObjectId(databaseId) })) as WithId<Record<string, any>> | null;
        if (!selectedPayment) {
            return reply.status(300).send({ error: 'No payment found with given database id or invoice id' });
        }
        reply.status(200).send(cleanDetails(selectedPayment as any));
    });
}


================================================
FILE: packages/backend/src/routes/paymentsByExtraId.ts
================================================
import { Static, Type } from '@sinclair/typebox';
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
import auth from '../middlewares/auth';
import mongoGenerator from '../mongo/generator';
import { WithId } from 'mongodb';
import { MongoTypeForRequest, cleanDetails, AdminRouteHeaders } from './types';
import { ForRequest, MongoPayment } from '../types';

const PaymentsByExtraIdResponse = Type.Array(MongoTypeForRequest, {
    description: `Successful response of payments`,
});

const PaymentsByExtraIdQueryString = Type.Object({
    extraId: Type.String({
        description: `The extraId of an existing payment`,
    }),
});

const opts: RouteShorthandOptions = {
    schema: {
        response: {
            200: PaymentsByExtraIdResponse,
        },
        querystring: PaymentsByExtraIdQueryString,
        headers: AdminRouteHeaders,
        tags: ['Payment'],
        description: 'Fetch an array of all payments by *extraId*. All payments by this query will appear here.',
        summary: 'Fetch an array of all payments by extraId',
        security: [
            {
                ApiKey: [],
            },
        ],
    },
    preHandler: auth,
};

export default async function createPaymentStatusRoute(server: FastifyInstance) {
    server.get<{
        Reply: Static<typeof PaymentsByExtraIdResponse> | string;
        Querystring: Static<typeof PaymentsByExtraIdQueryString>;
    }>('/getPaymentsByExtraId', opts, async (request, reply) => {
        const extraId = request.query.extraId;
        const { db } = await mongoGenerator();
        const selectedPayments = (await db.collection('payments').find({ extraId }).toArray()) as WithId<MongoPayment>[];
        const cleanedTransactions: ForRequest<MongoPayment>[] = selectedPayments.map((ele) => cleanDetails(ele));
        reply.status(200).send(cleanedTransactions);
    });
}


================================================
FILE: packages/backend/src/routes/swagger.ts
================================================
import { FastifyInstance } from 'fastify';
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUi from '@fastify/swagger-ui';
import fastifyPlugin from 'fastify-plugin';
import config from '../config';
import { MongoTypeForRequest } from './types';

export default fastifyPlugin(async function createInvoiceClientRoute(server: FastifyInstance) {
    server.register(fastifySwagger, {
        openapi: {
            info: {
                title: 'Keagate – A high-performance crypto payment gateway',
                version: '0.1.0',
                description: '',
            },
            externalDocs: {
                url: 'https://keagate.io',
                description: 'Find more info here',
            },
            tags: [
                {
                    name: 'Payment',
                    description:
                        "Payment lifecycle administrative routes. These routes require a valid `keagate-api-key` (set in local.json) and should never be directly invoked from your payees' machine.",
                },
                {
                    name: 'Invoice',
                    description:
                        "Publicly available routes that can be safely called from your payees' devices. These do not return sensitive information. Internally, these routes are used in the built-in invoice UI.",
                },
            ],
            servers: [
                {
                    url: config.has('HOST') ? config.getTyped('HOST') : 'http://YOUR_SERVER',
                },
            ],
            security: [],
            components: {
                securitySchemes: {
                    ApiKey: {
                        type: 'apiKey',
                        name: 'keagate-api-key',
                        in: 'header',
                    },
                },
            },
        }
    });

    server.register(fastifySwaggerUi, {
        routePrefix: '/docs',
        uiConfig: {
            // docExpansion: 'full',
            deepLinking: false,
        },
    });

    server.addSchema({
        $id: 'TypeForRequest',
        ...MongoTypeForRequest,
    });
});


================================================
FILE: packages/backend/src/routes/types.ts
================================================
import { Type } from '@sinclair/typebox';
import { WithId } from 'mongodb';
import { ForRequest, MongoPayment } from '../types';
import { encrypt } from '../utils';
import { paymentStatuses } from '@keagate/common';

// function StringEnum<T extends string[]>(values: [...T], options?: UnsafeOptions) {
//     return Type.Unsafe<T[number]>({ enum: values, ...options });
// }

export const MongoTypeForRequest = Type.Object(
    {
        publicKey: Type.String({
            description: `The destination address of the payment wallet for the client to send their assets to. 
        This wallet is controlled programmatically and will automatically deposit to your admin wallet as defined in \`/config/local.json\`.
        For most currencies, this address is generated uniquely upon newly created payments, except for some currencies like XRP 
        where a custom memo must be sent by the payee to be identified.`,
        }),
        amount: Type.Number({
            description: `The total value of the payment as defined in \`createPayment\`. Note that this is not necessarily the exact amount required
        before a payment is marked as complete. This depends on the value of *TRANSACTION_SLIPPAGE_TOLERANCE* in \`/config/local.json\` which dictates the
        percentage of a payment that is discounted from the total payment to create a smoother transaction process with network fees.
        `,
        }),
        amountPaid: Type.Number({
            description: `The total value that has been confirmed as being paid by the payee.`,
        }),
        expiresAt: Type.String({
            description: `An ISO 8601 timestamp detailing when a payment expires at. This value is calculated as [current date + *TRANSACTION_TIMEOUT* (a millisecond value in \`/config/local.json\`)].`,
        }),
        createdAt: Type.String({
            description: `An ISO 8601 timestamp detailing when a payment was created.`,
        }),
        updatedAt: Type.String({
            description: `An ISO 8601 timestamp detailing when a payment last received a status update.`,
        }),
        status: Type.String({
            description: `The current status of a payment. Can be one of: ${JSON.stringify(paymentStatuses)}.`,
        }),
        id: Type.String({
            description: `The internal id of a payment, also serves as Mongo's _id for the given record.`,
        }),
        extraId: Type.Optional(
            Type.String({
                description: `An optional extraId as defined in \`createPayment\`. This is useful for you manually managing the identity of payment.
        Example: An e-commerce store assigns some identification parameter upon a user's checkout for internal use. They create a new payment (via \`/createPayment\`) with 
        this value in the *extraId* parameter. The e-commerce store now doesn't have to manage Keagate's assigned *id*, since this *extraId* will be passed in the ipnCallback
        and can be used to find payments via \`/getPaymentsByExtraId\`.`,
            }),
        ),
        ipnCallbackUrl: Type.Optional(
            Type.String({
                format: 'uri',
                description: `An optional callback URL that will be invoked from Keagate when the status of a payment is updated. The request made by Keagate will
        be a POST request with the body being a JSON object of this same schema.`,
            }),
        ),
        invoiceCallbackUrl: Type.Optional(
            Type.String({
                format: 'uri',
                description: `An optional URL that payees will be re-directed to when their payment finalizes within the invoice interface. A query string parameter will
        be appended called *status* with the status of the payment. This re-direction will occur automatically.`,
            }),
        ),
        payoutTransactionHash: Type.Optional(
            Type.String({
                description: `The transaction id of the successful payment from the generated wallet to your admin wallet of the same currency. This can be used in the corresponding block explorer of this payment's currency.`,
            }),
        ),
        invoiceUrl: Type.String({
            description: `The path of a Keagate invoice that can be optionally used by your payee's. This is easier to use than the API-driven workflow, but
        is not required. Note that this is only a path and must be appended to your public domain that Keagate is running on.`,
        }),
        currency: Type.String({
            description: `The shorthand name of the selected payment's corresponding currency.`,
        }),
        walletIndex: Type.Optional(
            Type.Number({
                description: `Can be ignored and is typically only used for debugging purposes.`,
            }),
        ),
        memo: Type.Optional(
            Type.String({
                description: `If this payment currency doesn't support randomly generated wallets for each payment (such as XRP). The payee must pass this value
        as the memo or description of their transaction.`,
            }),
        ),
    },
    {
        description: `Successful response`,
    },
);

export function cleanDetails(details: MongoPayment | WithId<Omit<MongoPayment, 'id'>>): ForRequest<MongoPayment> {
    const id = 'id' in details ? details.id : details._id.toString();

    return {
        publicKey: details.publicKey,
        amount: details.amount,
        expiresAt: typeof details.expiresAt === 'string' ? details.expiresAt : details.expiresAt.toISOString(),
        createdAt: typeof details.createdAt === 'string' ? details.createdAt : details.createdAt.toISOString(),
        updatedAt: typeof details.updatedAt === 'string' ? details.updatedAt : details.updatedAt.toISOString(),
        status: details.status,
        id,
        extraId: details.extraId,
        ipnCallbackUrl: details.ipnCallbackUrl,
        invoiceCallbackUrl: details.invoiceCallbackUrl,
        payoutTransactionHash: details.payoutTransactionHash,
        invoiceUrl: `/invoice/${details.currency}/${encrypt(id)}`,
        currency: details.currency,
        amountPaid: details.amountPaid,
        ...({
            walletIndex: 'walletIndex' in details ? details.walletIndex : undefined,
            memo: 'memo' in details ? details.memo : undefined,
        } as any),
    };
}

export const AdminRouteHeaders = Type.Object({
    'keagate-api-key': Type.String({
        description: `The api key that you have set in \`/config/local.json\`. Default is "API-KEY", but must be changed for production use.`,
    }),
});

export const ErrorResponse = Type.Object(
    {
        error: Type.Optional(
            Type.String({
                description: `Error message string`,
            }),
        ),
    },
    {
        description: `Error response with message.`,
    },
);


================================================
FILE: packages/backend/src/transactionalWallets/GenericTransactionalWallet.ts
================================================
import dayjs from 'dayjs';
import { ObjectId } from 'mongodb';
import { AvailableCurrencies, ConcreteConstructor, PaymentStatusType } from '@keagate/common';
import mongoGenerator from '../mongo/generator';
import { MongoPayment, IFromNew, INativeInitInDatabase, ICoinlibInitInDatabase } from '../types';
import config from '../config';
import GenericAdminWallet from '../adminWallets/GenericAdminWallet';
import { AnyPayments } from 'coinlib-port';
import crypto from 'crypto';
import fetch from 'cross-fetch';
import logger from '../logger';

export default abstract class GenericTransactionalWallet {
    public currency: AvailableCurrencies;
    protected type: 'coinlib' | 'native';
Download .txt
gitextract_ifetv_mv/

├── .eslintrc.json
├── .gitignore
├── .nvmrc
├── LICENCE
├── README.md
├── TODO.md
├── assets/
│   └── coins.json
├── config/
│   └── default.json
├── package.json
├── packages/
│   ├── api-providers/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── GenericProvider.ts
│   │   │   ├── NowNodesProvider.ts
│   │   │   ├── QuickNodeProvider.ts
│   │   │   ├── SoChainProvider.ts
│   │   │   ├── TatumProvider.ts
│   │   │   ├── index.ts
│   │   │   └── units.ts
│   │   └── tsconfig.package.json
│   ├── backend/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── activityLoop.ts
│   │   │   ├── adminWallets/
│   │   │   │   ├── GenericAdminWallet.ts
│   │   │   │   ├── coinlib/
│   │   │   │   │   └── AdminCoinlibWrapper.ts
│   │   │   │   └── native/
│   │   │   │       ├── GenericNativeAdminWallet.ts
│   │   │   │       ├── Polygon/
│   │   │   │       │   └── index.ts
│   │   │   │       └── Solana/
│   │   │   │           ├── HdWallet.ts
│   │   │   │           └── index.ts
│   │   │   ├── config.ts
│   │   │   ├── context.ts
│   │   │   ├── currenciesToClients.ts
│   │   │   ├── devServer.ts
│   │   │   ├── index.ts
│   │   │   ├── limiters.ts
│   │   │   ├── logger.ts
│   │   │   ├── middlewares/
│   │   │   │   └── auth.ts
│   │   │   ├── mongo/
│   │   │   │   ├── generator.ts
│   │   │   │   └── index.ts
│   │   │   ├── routes/
│   │   │   │   ├── activePayments.ts
│   │   │   │   ├── createPayment.ts
│   │   │   │   ├── invoiceClient.ts
│   │   │   │   ├── invoiceStatus.ts
│   │   │   │   ├── paymentStatus.ts
│   │   │   │   ├── paymentsByExtraId.ts
│   │   │   │   ├── swagger.ts
│   │   │   │   └── types.ts
│   │   │   ├── transactionalWallets/
│   │   │   │   ├── GenericTransactionalWallet.ts
│   │   │   │   ├── coinlib/
│   │   │   │   │   ├── TransactionalCoinlibWrapper.ts
│   │   │   │   │   └── trxLimits.ts
│   │   │   │   └── native/
│   │   │   │       ├── GenericNativeTransactionalWallet.ts
│   │   │   │       ├── Polygon/
│   │   │   │       │   └── index.ts
│   │   │   │       └── Solana/
│   │   │   │           └── index.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   └── tsconfig.package.json
│   ├── common/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── config.ts
│   │   │   ├── currencies.ts
│   │   │   ├── fetch.ts
│   │   │   ├── index.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   └── tsconfig.package.json
│   ├── invoice-client/
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── src/
│   │   │   ├── components/
│   │   │   │   ├── App.tsx
│   │   │   │   ├── Invoice.tsx
│   │   │   │   └── ThreeDotsOverlay.tsx
│   │   │   ├── index.tsx
│   │   │   ├── styles/
│   │   │   │   └── globals.css
│   │   │   ├── utils/
│   │   │   │   ├── useDeviceSize.tsx
│   │   │   │   └── utils.ts
│   │   │   └── vite-env.d.ts
│   │   ├── tailwind.config.js
│   │   ├── tsconfig.json
│   │   ├── tsconfig.package.json
│   │   └── vite.config.ts
│   └── scripts/
│       ├── assets/
│       │   └── default.conf
│       ├── keagate.sh
│       ├── package.json
│       ├── src/
│       │   ├── DelegateLogger.ts
│       │   ├── configure.ts
│       │   ├── index.ts
│       │   ├── opts.ts
│       │   ├── setupMongo.ts
│       │   ├── setupNginx.ts
│       │   ├── setupSeeds.ts
│       │   └── setupWallets.ts
│       └── tsconfig.package.json
├── pnpm-workspace.yaml
├── prettier.config.js
├── tsconfig.base.json
├── tsconfig.json
└── tsconfig.project.json
Download .txt
SYMBOL INDEX (133 symbols across 49 files)

FILE: packages/api-providers/src/GenericProvider.ts
  method constructor (line 5) | constructor(..._: any[]) {

FILE: packages/api-providers/src/NowNodesProvider.ts
  class NowNodesProvider (line 8) | class NowNodesProvider extends GenericProvider {
    method constructor (line 11) | constructor(public apiKey: string, public currenciesToRpcUrls: Partial...
    method getBalance (line 15) | async getBalance(currency: AvailableCurrencies, address: string): Prom...
    method sendTransaction (line 40) | async sendTransaction(currency: AvailableCurrencies, hexTransaction: s...

FILE: packages/api-providers/src/SoChainProvider.ts
  class SoChainProvider (line 6) | class SoChainProvider extends GenericProvider {
    method getBalance (line 9) | async getBalance(currency: AvailableCurrencies, address: string): Prom...

FILE: packages/api-providers/src/TatumProvider.ts
  class TatumProvider (line 8) | class TatumProvider extends GenericProvider {
    method constructor (line 11) | constructor(public apiKey: string, public location: 'eu1' | 'us-west1') {
    method getBalance (line 15) | async getBalance(currency: AvailableCurrencies, address: string): Prom...
    method sendTransaction (line 64) | async sendTransaction(currency: AvailableCurrencies, hexTransaction: s...

FILE: packages/api-providers/src/index.ts
  type AvailableProviders (line 7) | type AvailableProviders = 'NowNodes' | 'Tatum';

FILE: packages/backend/src/activityLoop.ts
  class ActivityLoop (line 9) | class ActivityLoop {
    method start (line 13) | public start() {
    method stop (line 17) | public stop() {
    method runTxsCohort (line 21) | private async runTxsCohort(cohort: GenericTransactionalWallet[]) {
    method startBatch (line 31) | private async startBatch() {
    method checkSingleTransaction (line 64) | private checkSingleTransaction(trx: GenericTransactionalWallet): Promi...

FILE: packages/backend/src/adminWallets/GenericAdminWallet.ts
  method constructor (line 8) | constructor(constructor: CoinlibAdminConstructor | NativeAdminConstructo...
  method isValidAddress (line 15) | public isValidAddress(address: string): boolean {
  method setFromObject (line 19) | protected setFromObject(update: CoinlibAdminConstructor | NativeAdminCon...
  type RootAdminConstructor (line 26) | interface RootAdminConstructor {
  type CoinlibAdminConstructor (line 30) | interface CoinlibAdminConstructor extends RootAdminConstructor {
  type NativeAdminConstructor (line 34) | interface NativeAdminConstructor extends RootAdminConstructor {

FILE: packages/backend/src/adminWallets/coinlib/AdminCoinlibWrapper.ts
  class AdminCoinlibWrapper (line 6) | class AdminCoinlibWrapper extends GenericAdminWallet {
    method constructor (line 10) | constructor(constructor: CoinlibAdminConstructor) {
    method sendTransaction (line 22) | public async sendTransaction(destination: string, amount: number): Pro...
    method getBalance (line 33) | public async getBalance(): Promise<{ result: { confirmedBalance: numbe...

FILE: packages/backend/src/adminWallets/native/Polygon/index.ts
  class AdminPolygon (line 9) | class AdminPolygon extends GenericNativeAdminWallet {
    method constructor (line 14) | constructor(constructor: NativeAdminConstructor) {
    method getRandomRPC (line 20) | private getRandomRPC() {
    method getBalance (line 28) | async getBalance() {
    method sendTransaction (line 40) | async sendTransaction(destination: string, amount: number) {

FILE: packages/backend/src/adminWallets/native/Solana/HdWallet.ts
  constant DERIVATION_PATH (line 9) | const DERIVATION_PATH = {
  type ValueOf (line 15) | type ValueOf<T> = T[keyof T];
  function getKeyPairFromSeed (line 34) | function getKeyPairFromSeed(seed: string, walletIndex: number, derivatio...
  function generateKeypair (line 73) | function generateKeypair(walletIndex: number) {

FILE: packages/backend/src/adminWallets/native/Solana/index.ts
  class AdminSolana (line 10) | class AdminSolana extends GenericNativeAdminWallet {
    method constructor (line 15) | constructor(constructor: NativeAdminConstructor) {
    method getRandomRPC (line 20) | private getRandomRPC() {
    method getBalance (line 28) | async getBalance() {
    method sendTransaction (line 39) | async sendTransaction(destination: string, amount: number) {

FILE: packages/backend/src/context.ts
  class KeagateContext (line 13) | class KeagateContext {
    method init (line 27) | public async init() {
    method initEnabledCurrencies (line 35) | private initEnabledCurrencies() {
    method initActivePayments (line 48) | public async initActivePayments() {

FILE: packages/backend/src/currenciesToClients.ts
  function getCoinlibCurrencyToClient (line 29) | async function getCoinlibCurrencyToClient(): Promise<Record<typeof avail...

FILE: packages/backend/src/devServer.ts
  type IDevServer (line 5) | interface IDevServer {
  function createIPNCallbackServer (line 11) | function createIPNCallbackServer(): IDevServer {
  function devServer (line 53) | async function devServer(server: FastifyInstance) {

FILE: packages/backend/src/index.ts
  function main (line 34) | async function main() {

FILE: packages/backend/src/limiters.ts
  type ILimiterDetails (line 4) | interface ILimiterDetails {
  function generateLimiter (line 23) | function generateLimiter(ILimiterDetails: ILimiterDetails) {

FILE: packages/backend/src/logger.ts
  class DelegateLogger (line 3) | class DelegateLogger {

FILE: packages/backend/src/middlewares/auth.ts
  constant IP_WHITELIST (line 4) | let IP_WHITELIST: Set<string> = undefined;

FILE: packages/backend/src/mongo/generator.ts
  function generator (line 6) | async function generator() {

FILE: packages/backend/src/mongo/index.ts
  function getExistingPayments (line 10) | async function getExistingPayments() {

FILE: packages/backend/src/routes/activePayments.ts
  function createActivePaymentsRoute (line 28) | async function createActivePaymentsRoute(server: FastifyInstance) {

FILE: packages/backend/src/routes/createPayment.ts
  function createPaymentRoute (line 35) | async function createPaymentRoute(server: FastifyInstance) {

FILE: packages/backend/src/routes/invoiceStatus.ts
  function createInvoiceStatusRoute (line 37) | async function createInvoiceStatusRoute(server: FastifyInstance) {

FILE: packages/backend/src/routes/paymentStatus.ts
  function createPaymentStatusRoute (line 35) | async function createPaymentStatusRoute(server: FastifyInstance) {

FILE: packages/backend/src/routes/paymentsByExtraId.ts
  function createPaymentStatusRoute (line 38) | async function createPaymentStatusRoute(server: FastifyInstance) {

FILE: packages/backend/src/routes/types.ts
  function cleanDetails (line 94) | function cleanDetails(details: MongoPayment | WithId<Omit<MongoPayment, ...

FILE: packages/backend/src/transactionalWallets/GenericTransactionalWallet.ts
  method construct (line 48) | protected construct(constructor: CoinlibPaymentConstructor | NativePayme...
  method cashOut (line 52) | protected async cashOut(balance: number) {
  method initInDatabase (line 63) | protected async initInDatabase(obj: INativeInitInDatabase | ICoinlibInit...
  method setFromObject (line 83) | protected setFromObject(update: Partial<MongoPayment> | NativePaymentCon...
  method updateStatus (line 89) | protected async updateStatus(update: Partial<MongoPayment>, error?: stri...
  type PaymentConstructorRoot (line 132) | interface PaymentConstructorRoot {
  type NativePaymentConstructor (line 136) | interface NativePaymentConstructor extends PaymentConstructorRoot {
  type CoinlibPaymentConstructor (line 140) | interface CoinlibPaymentConstructor extends PaymentConstructorRoot {

FILE: packages/backend/src/transactionalWallets/coinlib/TransactionalCoinlibWrapper.ts
  class TransactionalCoinlibWrapper (line 10) | class TransactionalCoinlibWrapper extends GenericTransactionalWallet {
    method fromNew (line 16) | public async fromNew(obj: IFromNew, constructor: CoinlibPaymentConstru...
    method fromManual (line 32) | public fromManual(initObj: MongoPayment, constructor?: CoinlibPaymentC...
    method getDetails (line 43) | public getDetails(): CoinlibPayment {
    method checkTransaction (line 64) | public async checkTransaction(statusCallback: (status: PaymentStatusTy...
    method _cashOut (line 100) | protected async _cashOut(balance?: number) {
    method _getBalance (line 129) | protected async _getBalance(): Promise<number> {

FILE: packages/backend/src/transactionalWallets/coinlib/trxLimits.ts
  function randU32Sync (line 16) | function randU32Sync() {

FILE: packages/backend/src/transactionalWallets/native/GenericNativeTransactionalWallet.ts
  method fromManual (line 13) | public fromManual(initObj: MongoPayment, constructor?: NativePaymentCons...
  method getDetails (line 28) | public getDetails(): NativePayment {
  method checkTransaction (line 48) | public async checkTransaction(statusCallback: (status: PaymentStatusType...
  method _getBalance (line 82) | protected async _getBalance(): Promise<number> {
  method _cashOut (line 89) | protected async _cashOut(balance: number) {

FILE: packages/backend/src/transactionalWallets/native/Polygon/index.ts
  class TransactionalPolygon (line 7) | class TransactionalPolygon extends GenericTransactionalWallet {
    method fromNew (line 10) | async fromNew(obj: IFromNew, constructor: NativePaymentConstructor) {

FILE: packages/backend/src/transactionalWallets/native/Solana/index.ts
  class TransactionalSolana (line 8) | class TransactionalSolana extends GenericTransactionalWallet {
    method fromNew (line 11) | async fromNew(obj: IFromNew, constructor: NativePaymentConstructor) {

FILE: packages/backend/src/types.ts
  type PaymentRoot (line 4) | interface PaymentRoot {
  type NativePayment (line 21) | interface NativePayment extends PaymentRoot {
  type CoinlibPayment (line 27) | interface CoinlibPayment extends PaymentRoot {
  type ForRequest (line 34) | type ForRequest<T> = Omit<T, 'expiresAt' | 'createdAt' | 'updatedAt'> & {
  type MongoPayment (line 41) | type MongoPayment = CoinlibPayment | NativePayment;
  type IFromNew (line 43) | interface IFromNew {
  type INativeInitInDatabase (line 50) | interface INativeInitInDatabase extends IFromNew {
  type ICoinlibInitInDatabase (line 55) | interface ICoinlibInitInDatabase extends IFromNew {

FILE: packages/backend/src/utils.ts
  constant ENCRYPTION_KEY (line 12) | const ENCRYPTION_KEY = config.getTyped('INVOICE_ENC_KEY').padEnd(32, 'a'...
  constant IV_LENGTH (line 13) | const IV_LENGTH = 16;
  function encrypt (line 16) | function encrypt(text: string): string {
  function decrypt (line 23) | function decrypt(text: string): string {
  function randomSeedGenerator (line 31) | function randomSeedGenerator() {
  function requestRetry (line 37) | async function requestRetry<T>(request: (_?: any) => Promise<T>, delayMs...

FILE: packages/common/src/config.ts
  type MyCurrencyConfig (line 3) | type MyCurrencyConfig = Partial<
  type MyConfig (line 13) | interface MyConfig extends MyCurrencyConfig {

FILE: packages/common/src/currencies.ts
  type AvailableCurrencies (line 4) | type AvailableCurrencies = typeof availableCoinlibCurrencies[number] | t...

FILE: packages/common/src/fetch.ts
  function fPost (line 3) | async function fPost(route: string, body: Record<string, any>, headers?:...
  function fGet (line 12) | async function fGet(route: string, headers?: Record<string, any>): Promi...

FILE: packages/common/src/types.ts
  type ConcreteConstructor (line 2) | type ConcreteConstructor<T extends abstract new (...args: any) => any> =...
  type PaymentStatusType (line 8) | type PaymentStatusType = typeof paymentStatuses[number];

FILE: packages/common/src/utils.ts
  type NativeUtxo (line 1) | interface NativeUtxo {
  function convertChainsoToNativeUtxo (line 9) | function convertChainsoToNativeUtxo(Utxos: Record<string, any>[], addres...
  function arrayIncludes (line 23) | function arrayIncludes<T>(arr: T[], ele: any): ele is T {

FILE: packages/invoice-client/src/components/App.tsx
  function App (line 6) | function App() {

FILE: packages/invoice-client/src/components/Invoice.tsx
  function Invoice (line 23) | function Invoice() {

FILE: packages/invoice-client/src/components/ThreeDotsOverlay.tsx
  type IThreeDotsOverlay (line 4) | interface IThreeDotsOverlay {
  function ThreeDotsOverlay (line 11) | function ThreeDotsOverlay({ showDots, text, className, flashDots }: IThr...

FILE: packages/scripts/src/DelegateLogger.ts
  type LoggingLevels (line 1) | type LoggingLevels = 0 | 1 | 2;
  class DelegateLogger (line 3) | class DelegateLogger {
    method setLogLevel (line 6) | public setLogLevel(level: LoggingLevels) {

FILE: packages/scripts/src/configure.ts
  function main (line 21) | async function main() {

FILE: packages/scripts/src/opts.ts
  type Opts (line 1) | interface Opts {
  function setOpts (line 9) | function setOpts(newOpts: Opts) {

FILE: packages/scripts/src/setupMongo.ts
  constant DEFAULT_MONGO_CONN_STR (line 9) | const DEFAULT_MONGO_CONN_STR = 'mongodb://localhost:27017';
  function installMongo (line 11) | async function installMongo(): Promise<string> {
  function mongoFromConnectionString (line 42) | async function mongoFromConnectionString(mongoConnectionString: string):...
  function setupMongo (line 94) | async function setupMongo(): Promise<Partial<MyConfig>> {

FILE: packages/scripts/src/setupNginx.ts
  function _installNginx (line 8) | async function _installNginx(dockerString: string, host: string): Promis...
  function installNginxNoSSL (line 29) | async function installNginxNoSSL(): Promise<Partial<MyConfig>> {
  function installNginxWithSSL (line 44) | async function installNginxWithSSL(SSLDomains: string): Promise<Partial<...
  function setupNginx (line 61) | async function setupNginx(): Promise<Partial<MyConfig>> {

FILE: packages/scripts/src/setupSeeds.ts
  function randomSeedGenerator (line 4) | function randomSeedGenerator(length: number) {
  function setupSeeds (line 8) | async function setupSeeds(): Promise<Partial<MyConfig>> {

FILE: packages/scripts/src/setupWallets.ts
  function setupWallets (line 4) | async function setupWallets(): Promise<Partial<MyConfig>> {
Condensed preview — 93 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (294K chars).
[
  {
    "path": ".eslintrc.json",
    "chars": 446,
    "preview": "{\n    \"root\": true,\n    \"parser\": \"@typescript-eslint/parser\",\n    \"plugins\": [\n      \"@typescript-eslint\"\n    ],\n    \"e"
  },
  {
    "path": ".gitignore",
    "chars": 624,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": ".nvmrc",
    "chars": 2,
    "preview": "16"
  },
  {
    "path": "LICENCE",
    "chars": 3864,
    "preview": "Elastic License 2.0 (ELv2)\n\nURL: https://www.elastic.co/licensing/elastic-license\n\n## Acceptance\n\nBy using the software,"
  },
  {
    "path": "README.md",
    "chars": 23385,
    "preview": "<!-- markdownlint-disable MD033 MD041 -->\n\n<br />\n\n<!-- http://ipa-reader.xyz/?text=ki%3Age%C9%AAt&voice=Joey --->\n\n<h2 "
  },
  {
    "path": "TODO.md",
    "chars": 525,
    "preview": "# TODOs\n\n* ~~Flexible expire timer UI in invoice client~~\n  * ~~Formats like *DD-MM-YY hh:mm a* or *day-of-week hh:mm a*"
  },
  {
    "path": "assets/coins.json",
    "chars": 93982,
    "preview": "{\n    \"bitcoin\": [\n        {\n            \"address_type\": 0,\n            \"address_type_p2sh\": 5,\n            \"bech32_pref"
  },
  {
    "path": "config/default.json",
    "chars": 557,
    "preview": "{\n    \"some_ticker (e.g. 'DASH')\": {\n        \"ADMIN_PUBLIC_KEY\": \"\",\n        \"ADMIN_PRIVATE_KEY\": \"\"\n    },\n    \n\n    \"K"
  },
  {
    "path": "package.json",
    "chars": 1486,
    "preview": "{\n  \"name\": \"Keagate\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\\\"# Keagate\\\"\",\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n "
  },
  {
    "path": "packages/api-providers/package.json",
    "chars": 482,
    "preview": "{\n  \"name\": \"@keagate/api-providers\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\\\"# keagate\\\"\",\n  \"main\": \"build/index.js\""
  },
  {
    "path": "packages/api-providers/src/GenericProvider.ts",
    "chars": 478,
    "preview": "import { AvailableCurrencies } from '@keagate/common';\n\nexport default abstract class GenericProvider {\n    public suppo"
  },
  {
    "path": "packages/api-providers/src/NowNodesProvider.ts",
    "chars": 2647,
    "preview": "import GenericProvider from './GenericProvider';\nimport { AvailableCurrencies, fGet, fPost } from '@keagate/common';\nimp"
  },
  {
    "path": "packages/api-providers/src/QuickNodeProvider.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "packages/api-providers/src/SoChainProvider.ts",
    "chars": 993,
    "preview": "import GenericProvider from './GenericProvider';\nimport { AvailableCurrencies, fGet } from '@keagate/common';\n\n// https:"
  },
  {
    "path": "packages/api-providers/src/TatumProvider.ts",
    "chars": 3387,
    "preview": "import GenericProvider from './GenericProvider';\nimport { AvailableCurrencies, fGet, fPost, currencies } from '@keagate/"
  },
  {
    "path": "packages/api-providers/src/index.ts",
    "chars": 575,
    "preview": "import NowNodesProvider from './NowNodesProvider';\nimport SoChainProvider from './SoChainProvider';\nimport TatumProvider"
  },
  {
    "path": "packages/api-providers/src/units.ts",
    "chars": 990,
    "preview": "// https://github.com/markpenovich/cryptocurrency-unit-convert/blob/master/Units.json\n\nconst units = {\n    btc: {\n      "
  },
  {
    "path": "packages/api-providers/tsconfig.package.json",
    "chars": 277,
    "preview": "{\n\t\"extends\": \"../../tsconfig.base.json\",\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./build\",\n\t\t\"rootDir\": \"./src\",\n\t\t\"composit"
  },
  {
    "path": "packages/backend/package.json",
    "chars": 1602,
    "preview": "{\n  \"name\": \"@keagate/backend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\\\"# keagate\\\"\",\n  \"main\": \"build/index.js\",\n  \"s"
  },
  {
    "path": "packages/backend/src/activityLoop.ts",
    "chars": 2976,
    "preview": "import context from './context';\nimport GenericTransactionalWallet from './transactionalWallets/GenericTransactionalWall"
  },
  {
    "path": "packages/backend/src/adminWallets/GenericAdminWallet.ts",
    "chars": 1271,
    "preview": "import { availableCoinlibCurrencies, AvailableCurrencies } from '@keagate/common';\nimport WAValidator from 'multicoin-ad"
  },
  {
    "path": "packages/backend/src/adminWallets/coinlib/AdminCoinlibWrapper.ts",
    "chars": 2076,
    "preview": "import GenericAdminWallet, { CoinlibAdminConstructor } from '../GenericAdminWallet';\nimport { AnyPayments, CoinPayments,"
  },
  {
    "path": "packages/backend/src/adminWallets/native/GenericNativeAdminWallet.ts",
    "chars": 173,
    "preview": "import GenericAdminWallet from '../GenericAdminWallet';\n\nexport default abstract class GenericNativeAdminWallet extends "
  },
  {
    "path": "packages/backend/src/adminWallets/native/Polygon/index.ts",
    "chars": 2327,
    "preview": "import GenericNativeAdminWallet from '../GenericNativeAdminWallet';\nimport { ethers } from 'ethers';\nimport { NativeAdmi"
  },
  {
    "path": "packages/backend/src/adminWallets/native/Solana/HdWallet.ts",
    "chars": 2835,
    "preview": "// https://github.com/p2p-org/p2p-wallet-web/blob/2d894ae5893b8566760c8a52050fde7e918d3139/packages/core/src/contexts/se"
  },
  {
    "path": "packages/backend/src/adminWallets/native/Solana/index.ts",
    "chars": 2648,
    "preview": "import { Connection, clusterApiUrl, PublicKey, Keypair, Transaction, SystemProgram, LAMPORTS_PER_SOL, sendAndConfirmTran"
  },
  {
    "path": "packages/backend/src/config.ts",
    "chars": 605,
    "preview": "// https://itnext.io/node-config-made-type-safe-5be0a08ad5ba\nimport path from 'path';\n// TODO: Suppress NODE_APP_INSTANC"
  },
  {
    "path": "packages/backend/src/context.ts",
    "chars": 4805,
    "preview": "import { availableCoinlibCurrencies, AvailableCurrencies, availableNativeCurrencies, ConcreteConstructor, currencies, ar"
  },
  {
    "path": "packages/backend/src/currenciesToClients.ts",
    "chars": 1881,
    "preview": "import { availableCoinlibCurrencies, availableNativeCurrencies, ConcreteConstructor } from '@keagate/common';\nimport { A"
  },
  {
    "path": "packages/backend/src/devServer.ts",
    "chars": 4896,
    "preview": "import fastify, { FastifyInstance } from 'fastify';\nimport crypto from 'crypto';\nimport config from './config';\n\ninterfa"
  },
  {
    "path": "packages/backend/src/index.ts",
    "chars": 4522,
    "preview": "import config from './config';\nimport fastify from 'fastify';\nimport { currencies, availableCoinlibCurrencies, available"
  },
  {
    "path": "packages/backend/src/limiters.ts",
    "chars": 1147,
    "preview": "import { availableNativeCurrencies } from '@keagate/common';\nimport Bottleneck from 'bottleneck';\n\ninterface ILimiterDet"
  },
  {
    "path": "packages/backend/src/logger.ts",
    "chars": 409,
    "preview": "import config from './config';\n\nclass DelegateLogger {\n    log = (...args: any[]) => console.log('[KEAGATE LOG]: ', ...a"
  },
  {
    "path": "packages/backend/src/middlewares/auth.ts",
    "chars": 694,
    "preview": "import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from 'fastify';\nimport config from '../config';\n\nlet IP"
  },
  {
    "path": "packages/backend/src/mongo/generator.ts",
    "chars": 811,
    "preview": "import { MongoClient } from 'mongodb';\nimport config from '../config';\n\nlet clientInstance: MongoClient;\n\nexport default"
  },
  {
    "path": "packages/backend/src/mongo/index.ts",
    "chars": 398,
    "preview": "import { Db } from 'mongodb';\nimport mongoGenerator from './generator';\n\nconst _load = (db: Db, collection: 'payments') "
  },
  {
    "path": "packages/backend/src/routes/activePayments.ts",
    "chars": 1413,
    "preview": "import { Static, Type } from '@sinclair/typebox';\nimport { FastifyInstance, RouteShorthandOptions } from 'fastify';\nimpo"
  },
  {
    "path": "packages/backend/src/routes/createPayment.ts",
    "chars": 3713,
    "preview": "import { Static, Type } from '@sinclair/typebox';\nimport { FastifyInstance, RouteShorthandOptions } from 'fastify';\nimpo"
  },
  {
    "path": "packages/backend/src/routes/invoiceClient.ts",
    "chars": 1309,
    "preview": "import { FastifyInstance, RouteShorthandOptions } from 'fastify';\nimport fastifyStatic from '@fastify/static';\nimport pa"
  },
  {
    "path": "packages/backend/src/routes/invoiceStatus.ts",
    "chars": 2780,
    "preview": "import { Static, Type } from '@sinclair/typebox';\nimport { FastifyInstance, RouteShorthandOptions } from 'fastify';\nimpo"
  },
  {
    "path": "packages/backend/src/routes/paymentStatus.ts",
    "chars": 1941,
    "preview": "import { Static, Type } from '@sinclair/typebox';\nimport { FastifyInstance, RouteShorthandOptions } from 'fastify';\nimpo"
  },
  {
    "path": "packages/backend/src/routes/paymentsByExtraId.ts",
    "chars": 1876,
    "preview": "import { Static, Type } from '@sinclair/typebox';\nimport { FastifyInstance, RouteShorthandOptions } from 'fastify';\nimpo"
  },
  {
    "path": "packages/backend/src/routes/swagger.ts",
    "chars": 2162,
    "preview": "import { FastifyInstance } from 'fastify';\nimport fastifySwagger from '@fastify/swagger';\nimport fastifySwaggerUi from '"
  },
  {
    "path": "packages/backend/src/routes/types.ts",
    "chars": 6874,
    "preview": "import { Type } from '@sinclair/typebox';\nimport { WithId } from 'mongodb';\nimport { ForRequest, MongoPayment } from '.."
  },
  {
    "path": "packages/backend/src/transactionalWallets/GenericTransactionalWallet.ts",
    "chars": 5942,
    "preview": "import dayjs from 'dayjs';\nimport { ObjectId } from 'mongodb';\nimport { AvailableCurrencies, ConcreteConstructor, Paymen"
  },
  {
    "path": "packages/backend/src/transactionalWallets/coinlib/TransactionalCoinlibWrapper.ts",
    "chars": 5530,
    "preview": "import { CoinlibPayment, IFromNew, MongoPayment } from '../../types';\nimport config from '../../config';\nimport { AnyPay"
  },
  {
    "path": "packages/backend/src/transactionalWallets/coinlib/trxLimits.ts",
    "chars": 770,
    "preview": "import { availableCoinlibCurrencies } from '@keagate/common';\nimport crypto from 'crypto';\n\n// In pure unit\nexport const"
  },
  {
    "path": "packages/backend/src/transactionalWallets/native/GenericNativeTransactionalWallet.ts",
    "chars": 3752,
    "preview": "import { MongoPayment, NativePayment } from '../../types';\nimport config from '../../config';\nimport GenericAdminWallet "
  },
  {
    "path": "packages/backend/src/transactionalWallets/native/Polygon/index.ts",
    "chars": 1015,
    "preview": "import { NativePaymentConstructor } from '../../GenericTransactionalWallet';\nimport GenericTransactionalWallet from '../"
  },
  {
    "path": "packages/backend/src/transactionalWallets/native/Solana/index.ts",
    "chars": 1095,
    "preview": "import { NativePaymentConstructor } from '../../GenericTransactionalWallet';\nimport GenericTransactionalWallet from '../"
  },
  {
    "path": "packages/backend/src/types.ts",
    "chars": 1350,
    "preview": "import { AvailableCurrencies, PaymentStatusType } from '@keagate/common';\n\n// Inherited from all payment types\ninterface"
  },
  {
    "path": "packages/backend/src/utils.ts",
    "chars": 2388,
    "preview": "import crypto from 'crypto';\nimport { BIP32Factory, BIP32API } from 'bip32';\nimport * as ecc from 'tiny-secp256k1';\nimpo"
  },
  {
    "path": "packages/backend/tsconfig.package.json",
    "chars": 277,
    "preview": "{\n\t\"extends\": \"../../tsconfig.base.json\",\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./build\",\n\t\t\"rootDir\": \"./src\",\n\t\t\"composit"
  },
  {
    "path": "packages/common/package.json",
    "chars": 447,
    "preview": "{\n  \"name\": \"@keagate/common\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\\\"# keagate\\\"\",\n  \"main\": \"build/index.js\",\n  \"sc"
  },
  {
    "path": "packages/common/src/config.ts",
    "chars": 737,
    "preview": "import { AvailableCurrencies } from './currencies';\n\ntype MyCurrencyConfig = Partial<\n    Record<\n        AvailableCurre"
  },
  {
    "path": "packages/common/src/currencies.ts",
    "chars": 1728,
    "preview": "export const availableCoinlibCurrencies = ['LTC', 'BTC', 'ETH', 'DOGE', 'DASH'] as const;\nexport const availableNativeCu"
  },
  {
    "path": "packages/common/src/fetch.ts",
    "chars": 519,
    "preview": "import fetch from 'cross-fetch';\n\nexport async function fPost(route: string, body: Record<string, any>, headers?: Record"
  },
  {
    "path": "packages/common/src/index.ts",
    "chars": 131,
    "preview": "export * from './fetch';\nexport * from './currencies';\nexport * from './utils';\nexport * from './types';\nexport * from '"
  },
  {
    "path": "packages/common/src/types.ts",
    "chars": 431,
    "preview": "// https://stackoverflow.com/a/66702014\nexport type ConcreteConstructor<T extends abstract new (...args: any) => any> = "
  },
  {
    "path": "packages/common/src/utils.ts",
    "chars": 711,
    "preview": "interface NativeUtxo {\n    txId: string;\n    outputIndex: number;\n    address: string;\n    script: string;\n    satoshis:"
  },
  {
    "path": "packages/common/tsconfig.package.json",
    "chars": 277,
    "preview": "{\n\t\"extends\": \"../../tsconfig.base.json\",\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./build\",\n\t\t\"rootDir\": \"./src\",\n\t\t\"composit"
  },
  {
    "path": "packages/invoice-client/index.html",
    "chars": 664,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "packages/invoice-client/package.json",
    "chars": 1100,
    "preview": "{\n  \"name\": \"@keagate/invoice-client\",\n  \"scripts\": {\n    \"pureDev\": \"vite\",\n    \"dev\": \"tsc --project tsconfig.package."
  },
  {
    "path": "packages/invoice-client/postcss.config.js",
    "chars": 82,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "packages/invoice-client/src/components/App.tsx",
    "chars": 747,
    "preview": "import { Flowbite } from 'flowbite-react';\nimport { Toaster } from 'react-hot-toast';\nimport Invoice from './Invoice';\ni"
  },
  {
    "path": "packages/invoice-client/src/components/Invoice.tsx",
    "chars": 14607,
    "preview": "import { Spinner, Alert } from 'flowbite-react';\nimport { ReactComponent as BtcIcon } from 'cryptocurrency-icons/svg/col"
  },
  {
    "path": "packages/invoice-client/src/components/ThreeDotsOverlay.tsx",
    "chars": 1651,
    "preview": "import React, { useState, useEffect } from 'react';\nimport clsx from 'clsx';\n\ninterface IThreeDotsOverlay {\n    showDots"
  },
  {
    "path": "packages/invoice-client/src/index.tsx",
    "chars": 254,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport 'tailwindcss/tailwind.css';\nimport App from './compo"
  },
  {
    "path": "packages/invoice-client/src/styles/globals.css",
    "chars": 1202,
    "preview": "@import \"tailwindcss/base\";\n@import \"tailwindcss/components\";\n@import \"tailwindcss/utilities\";\n\nhtml, body {\n  padding: "
  },
  {
    "path": "packages/invoice-client/src/utils/useDeviceSize.tsx",
    "chars": 742,
    "preview": "import { useState, useEffect } from 'react';\n\nconst useDeviceSize = () => {\n    const [width, setWidth] = useState<numbe"
  },
  {
    "path": "packages/invoice-client/src/utils/utils.ts",
    "chars": 1012,
    "preview": "import toast from 'react-hot-toast';\n\nexport const copyToClipboard = (value: string, successText: string) => {\n    if (!"
  },
  {
    "path": "packages/invoice-client/src/vite-env.d.ts",
    "chars": 88,
    "preview": "/// <reference types=\"vite/client\" />\n/// <reference types=\"vite-plugin-svgr/client\" />\n"
  },
  {
    "path": "packages/invoice-client/tailwind.config.js",
    "chars": 1252,
    "preview": "/** @type {import('tailwindcss/tailwind-config').TailwindConfig} */\nconst config = {\n  content: [\n    \"./src/components/"
  },
  {
    "path": "packages/invoice-client/tsconfig.json",
    "chars": 182,
    "preview": "{\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"noEmit\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\n   "
  },
  {
    "path": "packages/invoice-client/tsconfig.package.json",
    "chars": 375,
    "preview": "{\n\t\"extends\": \"../../tsconfig.base.json\",\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./build\",\n\t\t\"rootDir\": \"./src\",\n\t\t\"composit"
  },
  {
    "path": "packages/invoice-client/vite.config.ts",
    "chars": 548,
    "preview": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\nimport tsconfigPaths from 'vite-tsconfig-pa"
  },
  {
    "path": "packages/scripts/assets/default.conf",
    "chars": 646,
    "preview": "server {\n\tlisten 80 default_server;\n\tlisten [::]:80 default_server;\n    server_name _;\n\n    location / {\n        proxy_p"
  },
  {
    "path": "packages/scripts/keagate.sh",
    "chars": 6965,
    "preview": "#!/bin/bash\n\n{ # this ensures the entire script is downloaded #\n\n    # OS=\"$(uname -s)\"\n    if [ -n \"$SUDO_USER\" ]; then"
  },
  {
    "path": "packages/scripts/package.json",
    "chars": 787,
    "preview": "{\n  \"name\": \"@keagate/scripts\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\\\"# keagate\\\"\",\n  \"main\": \"build/index.js\",\n  \"s"
  },
  {
    "path": "packages/scripts/src/DelegateLogger.ts",
    "chars": 562,
    "preview": "export type LoggingLevels = 0 | 1 | 2;\n\nclass DelegateLogger {\n    logLevel: LoggingLevels = 1;\n\n    public setLogLevel("
  },
  {
    "path": "packages/scripts/src/configure.ts",
    "chars": 4650,
    "preview": "import { MyConfig } from '@keagate/common';\nimport { program } from 'commander';\nimport logger, { LoggingLevels } from '"
  },
  {
    "path": "packages/scripts/src/index.ts",
    "chars": 45,
    "preview": "export default null;\n// Just for entry point\n"
  },
  {
    "path": "packages/scripts/src/opts.ts",
    "chars": 197,
    "preview": "interface Opts {\n    quiet?: boolean;\n    verbose?: boolean;\n    dryrun?: boolean;\n}\n\nlet _opts: Opts;\n\nexport function "
  },
  {
    "path": "packages/scripts/src/setupMongo.ts",
    "chars": 5517,
    "preview": "import { MongoClient } from 'mongodb';\nimport prompts from 'prompts';\nimport spawnAsync from '@expo/spawn-async';\nimport"
  },
  {
    "path": "packages/scripts/src/setupNginx.ts",
    "chars": 3848,
    "preview": "import spawnAsync from '@expo/spawn-async';\nimport { MyConfig, fGet } from '@keagate/common';\nimport kleur from 'kleur';"
  },
  {
    "path": "packages/scripts/src/setupSeeds.ts",
    "chars": 588,
    "preview": "import { MyConfig } from '@keagate/common';\nimport crypto from 'crypto';\n\nfunction randomSeedGenerator(length: number) {"
  },
  {
    "path": "packages/scripts/src/setupWallets.ts",
    "chars": 2118,
    "preview": "import { MyConfig, availableCoinlibCurrencies, availableNativeCurrencies, AvailableCurrencies, currencies } from '@keaga"
  },
  {
    "path": "packages/scripts/tsconfig.package.json",
    "chars": 277,
    "preview": "{\n\t\"extends\": \"../../tsconfig.base.json\",\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./build\",\n\t\t\"rootDir\": \"./src\",\n\t\t\"composit"
  },
  {
    "path": "pnpm-workspace.yaml",
    "chars": 26,
    "preview": "packages:\n  - 'packages/*'"
  },
  {
    "path": "prettier.config.js",
    "chars": 195,
    "preview": "module.exports = {\n    printWidth: 160,\n    singleQuote: true,\n    trailingComma: 'all',\n    semi: true,\n    jsxSingleQu"
  },
  {
    "path": "tsconfig.base.json",
    "chars": 316,
    "preview": "{\n    \"compilerOptions\": {\n        \"declaration\": true,\n        \"resolveJsonModule\": true,\n        \"sourceMap\": true,\n  "
  },
  {
    "path": "tsconfig.json",
    "chars": 227,
    "preview": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"baseUrl\": \".\",\n        \"noEmit\": true,\n      "
  },
  {
    "path": "tsconfig.project.json",
    "chars": 482,
    "preview": "{\n    \"files\": [],\n    \"references\": [\n        {\n            \"path\": \"./packages/common/tsconfig.package.json\",\n        "
  }
]

About this extraction

This page contains the full source code of the dilan-dio4/Keagate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 93 files (265.5 KB), approximately 69.2k tokens, and a symbol index with 133 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!