> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liquidramp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Provision Node Setup

> Deploy the Liquidramp provision node on DigitalOcean or AWS and register its URL in the portal.

Liquidity providers run a **provision node** — a small always-on server that Liquidramp calls to execute fiat payouts (via your BaaS/PSP) and crypto transfers (via your EVM wallet). This guide is the low-cost Docker path on a single VM.

You do **not** need this page if you only consume the Partner API (onramp, offramp, swap).

```mermaid theme={null}
sequenceDiagram
    participant Portal as Partner portal
    participant API as Liquidramp API
    participant Node as Your provision node
    participant Rails as BaaS / chain

    Portal->>API: Save endpoint_url
    API->>Node: GET /health
    Node-->>API: healthy
    API->>Node: HMAC-signed fiat or crypto request
    Node->>Rails: Payout or transfer
    Node->>API: Heartbeat POST /v1/node-info
```

## What you need first

1. KYB-approved provider account in the [partner portal](https://dashboard.liquidramp.com)
2. API keys (`pk_*`, `sk_*`, `enc_*`) and your partner reference (`liquidramp-client-id`) — see [Authentication](/getting-started/authentication)
3. Provision node source (the Docker-ready `liquidramp-node` project)
4. At least one fulfilment rail:
   * **Fiat:** PalmPay, BellBank, Korapay, or Nomba credentials
   * **Crypto:** an EVM wallet private key plus the currencies/networks you will serve
5. A domain (for example `node.yourcompany.com`) pointed at the VM

## Recommended host

Keep it small. The node is an HTTP process plus outbound API calls.

|                   | Minimum          | Comfortable      |
| ----------------- | ---------------- | ---------------- |
| vCPU              | 1                | 1                |
| RAM               | 1 GB             | 2 GB             |
| Disk              | 20 GB SSD        | 25–40 GB SSD     |
| OS                | Ubuntu 24.04 LTS | Ubuntu 24.04 LTS |
| Monthly (typical) | \~US\$6–12       | \~US\$12–20      |

Open only **22** (SSH), **80**, and **443**. Do not publish the node’s app port (3100) to the internet — terminate TLS on the host and proxy locally.

<Warning>
  Production `endpoint_url` must be **HTTPS**. Saving the URL in the portal fails unless `GET /health` on that host returns 200.
</Warning>

## 1. Create a cheap VM

Pick **one** of the following. Both are single-server setups — no load balancer, Kubernetes, or managed container service required.

### DigitalOcean Droplet

1. Create a Droplet: **Ubuntu 24.04**, **Regular SSD**, **1 vCPU / 2 GB RAM** (Basic, \~US\$12/mo). The 1 GB plan works for sandbox only.
2. Add your SSH key. Enable a **Reserved IP** if you want a stable address across rebuilds.
3. In your DNS provider, create an A record: `node.yourcompany.com` → the Droplet (or reserved) IPv4.
4. SSH in as `root` (or the user you created).

### AWS Lightsail (lightweight AWS)

Lightsail is the low-cost AWS option. A t-family EC2 instance plus ALB is unnecessary for a single node.

1. In [Lightsail](https://lightsail.aws.amazon.com), create an instance: **OS Only → Ubuntu 24.04**, plan **$10/mo** (2 GB) or **$5/mo** (1 GB, sandbox).
2. Create a **static IP** and attach it to the instance.
3. Networking firewall: allow **SSH (22)**, **HTTP (80)**, **HTTPS (443)**.
4. Point `node.yourcompany.com` at the static IP.
5. SSH in with the Lightsail key pair.

## 2. Harden and install Docker

Run on the VM:

```bash theme={null}
apt-get update && apt-get upgrade -y
apt-get install -y ca-certificates curl ufw

# SSH keys only — disable password logins in /etc/ssh/sshd_config if you have not already

ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable

# Docker Engine + Compose plugin
curl -fsSL https://get.docker.com | sh
```

Confirm:

```bash theme={null}
docker --version
docker compose version
```

## 3. Place the node and environment file

Copy the provision node project onto the host (git clone of the repo you were given, or `scp -r`). Example layout:

```
/opt/liquidramp-node/
  Dockerfile
  docker-compose.yml   ← you create this (below)
  .env                 ← never commit this
```

Create `/opt/liquidramp-node/docker-compose.yml`:

```yaml theme={null}
services:
  provision-node:
    build: .
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "127.0.0.1:3100:3100"
    healthcheck:
      test:
        [
          "CMD",
          "node",
          "-e",
          "require('http').get('http://localhost:3100/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
        ]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 20s
```

Binding `127.0.0.1:3100` keeps the app off the public interface. Caddy (next step) is the only public entrypoint.

Create `/opt/liquidramp-node/.env` from the project’s `.env.example`. Required for every node:

```env theme={null}
NODE_ENV=production
HOST=0.0.0.0
PORT=3100
LOG_LEVEL=warn
TZ=UTC
HEARTBEAT_INTERVAL_SECS=1800

LIQUIDRAMP_API_BASE_URL=https://vibe-api.liquidramp.com
LIQUIDRAMP_CLIENT_ID=LR-ABC123
LIQUIDRAMP_SECRET_KEY=sk_test_...
LIQUIDRAMP_ENCRYPTION_KEY=enc_test_...
```

Use `https://vibe-api.liquidramp.com` with `*_test_*` keys on test, and `https://api.liquidramp.com` with `*_live_*` keys in production. `LIQUIDRAMP_API_BASE_URL` is the **root** host (no `/v1`). The node calls `/v1/node-info` and `/v1/lp/orders/...` itself.

### Fiat provision (optional)

Set `FIAT_CURRENCIES` and **only** the BaaS you actually hold. Providers that are missing credentials are skipped at startup.

```env theme={null}
FIAT_CURRENCIES=NGN

# PalmPay
PALMPAY_APP_ID=
PALMPAY_MERCHANT_ID=
PALMPAY_ACCOUNT_NO=
PALMPAY_MERCHANT_PRIVATE_KEY=

# BellBank
BELLBANK_CONSUMER_KEY=
BELLBANK_CONSUMER_SECRET=

# Korapay
KORAPAY_ACCOUNT_NAME=
KORAPAY_ACCOUNT_NO=
KORAPAY_SECRET_KEY=

# Nomba
NOMBA_CLIENT_ID=
NOMBA_CLIENT_SECRET=
NOMBA_ACCOUNT_ID=
NOMBA_ACCOUNT_NO=
# NOMBA_SUB_ACCOUNT_ID=
```

BaaS payment webhooks are registered against the **Liquidramp API** (`/v1/webhooks/baas/...`), not against your VM. You do not need extra inbound webhook ports on the node.

### Crypto provision (optional)

```env theme={null}
CRYPTO_CURRENCIES=USDC,USDT
CRYPTO_NETWORKS=BSC,BASE,POLYGON
EVM_WALLET_PRIVATE_KEY=0x...
```

Optional RPC keys improve reliability: `ALCHEMY_API_KEY`, `INFURA_PROJECT_ID`, `ANKR_API_KEY`, `DRPC_API_KEY`.

<Warning>
  `EVM_WALLET_PRIVATE_KEY` and BaaS secrets must never be committed or pasted into chat logs. Creating a new Liquidramp key set in the portal **revokes** the previous set — update `.env` and recreate the container.
</Warning>

If you use an [IP whitelist](/getting-started/authentication#ip-whitelist) on the partner profile, add this VM’s **egress** IPv4 so heartbeat and fulfilment callbacks to the Liquidramp API are not blocked.

## 4. HTTPS with Caddy

Caddy obtains Let’s Encrypt certificates automatically.

```bash theme={null}
apt-get install -y caddy
```

`/etc/caddy/Caddyfile`:

```
node.yourcompany.com {
    reverse_proxy 127.0.0.1:3100
}
```

```bash theme={null}
systemctl reload caddy
```

Wait until DNS has propagated. Then:

```bash theme={null}
cd /opt/liquidramp-node
docker compose up -d --build
curl -sS https://node.yourcompany.com/health
```

Expected: HTTP 200 with `"message": "Healthy"` (no HMAC required on `/health`).

## 5. Register the URL in the portal

Liquidramp does not discover your node. You must save the public URL.

1. Open [Settings → Node Server](https://dashboard.liquidramp.com/settings/node-server) in the [partner portal](https://dashboard.liquidramp.com).
2. Enter the URL, for example `https://node.yourcompany.com`.
   * Include `https://`
   * **No trailing slash**
   * Do not append `/health` or `/v1`
3. Save. The platform immediately calls `GET {endpoint_url}/health`. If that fails, the URL is rejected.
4. After a successful save, the Node Server page shows status, version, environment, and client ID. Use **Refresh** to re-fetch `GET /node-info`.

You cannot create liquidity provisions until `endpoint_url` is set and the node is reachable.

## 6. Confirm it is live

| Check     | How                                                                          |
| --------- | ---------------------------------------------------------------------------- |
| Health    | `curl https://node.yourcompany.com/health` → 200                             |
| Portal    | Settings → Node Server shows **healthy**                                     |
| Heartbeat | Container logs show `Heartbeat sent successfully` (default every 30 minutes) |
| Outbound  | Node can reach `LIQUIDRAMP_API_BASE_URL` (firewall/DNS/whitelist)            |

Heartbeat payload includes node info, wallet address (if configured), BaaS accounts, and balances. The platform uses that to route orders to you.

## Operations

* `docker compose logs -f` for live logs; `docker compose restart` after `.env` changes (`--force-recreate` if env did not pick up).
* `restart: unless-stopped` brings the node back after a reboot.
* Keep Ubuntu patched. Prefer SSH keys only.
* Rotate Liquidramp and BaaS credentials on a schedule; rotate `enc_*` together with `sk_*`.
* Alert if `/health` fails or heartbeat errors persist — an offline node stops receiving assignments.

## Troubleshooting

| Symptom                                   | Likely cause                                                                                         |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Portal save fails / “Node request failed” | DNS not pointing here, Caddy not serving 443, or container not listening on 3100                     |
| `401` on `/node-info`                     | Expected without HMAC. Portal and platform sign with your `enc_*` key. Health is the unsigned check. |
| Heartbeat fails                           | Wrong `LIQUIDRAMP_*` keys, API host mismatch (test vs live), or IP whitelist missing this VM         |
| BaaS provider skipped at boot             | Missing env vars for that provider — check container logs                                            |
| Crypto transfers fail                     | Invalid `EVM_WALLET_PRIVATE_KEY`, empty `CRYPTO_NETWORKS`, or RPC connectivity                       |

## Related

* [Create account](/getting-started/create-account)
* [Authentication](/getting-started/authentication)
* [Sandbox vs production](/getting-started/sandbox-vs-production)
* [Going live](/getting-started/checklist)
* [Security](/compliance/overview)
