# 8mix API

**Base URL:** `https://api.8mix.gg`

***

Non-custodial privacy mixer for TRC-20 USDT on TRON. 13 endpoints for pool queries, unsigned transaction building, and relayer-assisted withdrawals.

The API never handles user secrets — note generation, ZK proof computation, and private key management are strictly client-side.

***

### Response Format

All responses follow the same JSON envelope:

```json
{ "ok": true, "data": { ... } }
```

```json
{ "ok": false, "error": "Error description" }
```

***

### Rate Limits

| Scope                        | Limit        | Window     |
| ---------------------------- | ------------ | ---------- |
| All `/api/*` endpoints       | 120 requests | 60 seconds |
| `POST /api/relayer/withdraw` | 5 requests   | 60 seconds |

Rate limit headers included in every response: `ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset`.

***

### Denominations

Valid denominations: `100`, `500`, `1000, 3000`, `5000`, `10000` (USDT).

All raw values are in SUN (6 decimals): 100 USDT = `100000000`.

***

### Pools

#### `GET /api/pools`

List all pools with balance, Merkle root, and relayer fee.

```bash
curl https://api.8mix.gg/api/pools
```

```json
{
  "ok": true,
  "data": [
    {
      "denomination": 100,
      "denominationRaw": "100000000",
      "address": "TEd3WceUmLB25bqygmk6zgZjhAYnSryTNc",
      "poolBalance": 100,
      "poolBalanceRaw": "100000000",
      "currentRoot": "0x27e7a94282f4cc499598f5b2f24ac6c02443d9a0892d51260ef3d180108a928b",
      "relayerFee": 4
    },
    {
      "denomination": 1000,
      "denominationRaw": "1000000000",
      "address": "TCY3LAEtVKicxaP1jDC47KSVvykeB2nwhX",
      "poolBalance": 0,
      "poolBalanceRaw": "0",
      "currentRoot": "0x19df90ec844ebc4ffeebd866f33859b0c051d8c958ee3aa88f8f8df3db91a5b1",
      "relayerFee": 4
    },
    {
      "denomination": 3000,
      "denominationRaw": "3000000000",
      "address": "TVSY8gM1tGSBgzT3naiRmRAbCmyn6ZUdjA",
      "poolBalance": 0,
      "poolBalanceRaw": "0",
      "currentRoot": "0x19df90ec844ebc4ffeebd866f33859b0c051d8c958ee3aa88f8f8df3db91a5b1",
      "relayerFee": 4
    }
  ]
}
```

| Field             | Type   | Description                            |
| ----------------- | ------ | -------------------------------------- |
| `denomination`    | number | Pool denomination in USDT              |
| `denominationRaw` | string | Denomination in SUN (6 decimals)       |
| `address`         | string | Pool contract address (base58)         |
| `poolBalance`     | number | Current pool USDT balance              |
| `poolBalanceRaw`  | string | Pool balance in SUN                    |
| `currentRoot`     | string | Current Merkle tree root (bytes32 hex) |
| `relayerFee`      | number | Relayer fee in USDT                    |

***

#### `GET /api/pools/:denom`

Single pool info. Returns `relayerFeeRaw` in addition to list fields.

```bash
curl https://api.8mix.gg/api/pools/100
```

```json
{
  "ok": true,
  "data": {
    "denomination": 100,
    "denominationRaw": "100000000",
    "address": "TEd3WceUmLB25bqygmk6zgZjhAYnSryTNc",
    "poolBalance": 100,
    "poolBalanceRaw": "100000000",
    "currentRoot": "0x27e7a94282f4cc499598f5b2f24ac6c02443d9a0892d51260ef3d180108a928b",
    "relayerFee": 4,
    "relayerFeeRaw": "4000000"
  }
}
```

***

#### `GET /api/pools/:denom/root`

Current Merkle tree root for a pool.

```bash
curl https://api.8mix.gg/api/pools/100/root
```

```json
{
  "ok": true,
  "data": { "root": "0x27e7a94282f4cc499598f5b2f24ac6c02443d9a0892d51260ef3d180108a928b" }
}
```

***

#### `POST /api/pools/:denom/check-spent`

Check if a nullifier has been spent (withdrawal already processed).

```bash
curl -X POST https://api.8mix.gg/api/pools/100/check-spent \
  -H "Content-Type: application/json" \
  -d '{"nullifierHash": "0x0000000000000000000000000000000000000000000000000000000000000001"}'
```

| Body field      | Type   | Required | Format                        |
| --------------- | ------ | -------- | ----------------------------- |
| `nullifierHash` | string | yes      | `0x` + 64 hex chars (bytes32) |

```json
{ "ok": true, "data": { "spent": false } }
```

***

#### `POST /api/pools/:denom/can-withdraw`

Pre-check withdrawal validity before generating a proof. Verifies that the nullifier is not spent and the root is known.

```bash
curl -X POST https://api.8mix.gg/api/pools/100/can-withdraw \
  -H "Content-Type: application/json" \
  -d '{
    "nullifierHash": "0x0000000000000000000000000000000000000000000000000000000000000001",
    "root": "0x27e7a94282f4cc499598f5b2f24ac6c02443d9a0892d51260ef3d180108a928b"
  }'
```

| Body field      | Type   | Required | Format      |
| --------------- | ------ | -------- | ----------- |
| `nullifierHash` | string | yes      | bytes32 hex |
| `root`          | string | yes      | bytes32 hex |

**Possible responses:**

```json
{ "ok": true, "data": { "valid": true, "reason": "" } }
{ "ok": true, "data": { "valid": false, "reason": "unknown root" } }
{ "ok": true, "data": { "valid": false, "reason": "already spent" } }
```

***

### Wallet Queries

#### `GET /api/balance/:address`

USDT balance of a TRON address.

```bash
curl https://api.8mix.gg/api/balance/TAWHckA571Cu3h8njup8Bv5oqXGdtwuX2T
```

```json
{
  "ok": true,
  "data": { "balance": 26, "balanceRaw": "26331919" }
}
```

| Field        | Type   | Description                   |
| ------------ | ------ | ----------------------------- |
| `balance`    | number | USDT balance (human-readable) |
| `balanceRaw` | string | Balance in SUN                |

***

#### `GET /api/allowance/:address/:denom`

USDT allowance granted by address to the pool contract.

```bash
curl https://api.8mix.gg/api/allowance/TAWHckA571Cu3h8njup8Bv5oqXGdtwuX2T/100
```

```json
{
  "ok": true,
  "data": { "allowance": "0", "sufficient": false }
}
```

| Field        | Type    | Description                         |
| ------------ | ------- | ----------------------------------- |
| `allowance`  | string  | Current allowance in SUN            |
| `sufficient` | boolean | `true` if allowance >= denomination |

***

### Transaction Builders

These endpoints return **unsigned** TronWeb-compatible transaction objects. The client must sign with the user's wallet and broadcast to TRON.

#### `POST /api/tx/approve`

Build an unsigned USDT approve transaction.

```bash
curl -X POST https://api.8mix.gg/api/tx/approve \
  -H "Content-Type: application/json" \
  -d '{"owner": "TUserAddress...", "denomination": "100"}'
```

| Body field     | Type   | Required | Description              |
| -------------- | ------ | -------- | ------------------------ |
| `owner`        | string | yes      | TRON address (base58)    |
| `denomination` | string | yes      | `100`, `1000`, or `3000` |

```json
{ "ok": true, "data": { "transaction": { ... } } }
```

Sign with `tronWeb.trx.sign(transaction)` and broadcast with `tronWeb.trx.sendRawTransaction(signed)`.

***

#### `POST /api/tx/deposit`

Build an unsigned deposit transaction. User must have approved USDT first.

```bash
curl -X POST https://api.8mix.gg/api/tx/deposit \
  -H "Content-Type: application/json" \
  -d '{"owner": "TUserAddress...", "denomination": "100", "commitment": "0xabc...def"}'
```

| Body field     | Type   | Required | Format                                      |
| -------------- | ------ | -------- | ------------------------------------------- |
| `owner`        | string | yes      | TRON address (base58)                       |
| `denomination` | string | yes      | `100`, `1000`, or `3000`                    |
| `commitment`   | string | yes      | bytes32 hex — `Poseidon(nullifier, secret)` |

```json
{ "ok": true, "data": { "transaction": { ... } } }
```

***

#### `POST /api/tx/withdraw`

Build an unsigned withdraw transaction. User pays gas. For gasless withdrawal, use `/api/relayer/withdraw` instead.

```bash
curl -X POST https://api.8mix.gg/api/tx/withdraw \
  -H "Content-Type: application/json" \
  -d '{
    "caller": "TUserAddress...",
    "denomination": "100",
    "proof": "0x...",
    "root": "0x...",
    "nullifierHash": "0x...",
    "recipient": "TRecipient...",
    "relayer": "TRelayer..."
  }'
```

| Body field      | Type   | Required | Format                                  |
| --------------- | ------ | -------- | --------------------------------------- |
| `caller`        | string | yes      | TRON address (who signs the tx)         |
| `denomination`  | string | yes      | `100`, `1000`, or `3000`                |
| `proof`         | string | yes      | `0x` + 512 hex chars (Groth16 proof)    |
| `root`          | string | yes      | bytes32 hex                             |
| `nullifierHash` | string | yes      | bytes32 hex                             |
| `recipient`     | string | yes      | TRON address                            |
| `relayer`       | string | no       | TRON address (defaults to 8mix relayer) |

```json
{ "ok": true, "data": { "transaction": { ... } } }
```

***

### Relayer

The relayer signs and broadcasts withdrawal transactions on behalf of users, covering all gas/energy costs. A fixed fee of **4 USDT** is deducted from the withdrawal amount.

#### `GET /api/relayer/status`

Check if the relayer is configured and available.

```bash
curl https://api.8mix.gg/api/relayer/status
```

```json
{
  "ok": true,
  "data": { "enabled": true, "address": "TAWHckA571Cu3h8njup8Bv5oqXGdtwuX2T" }
}
```

| Field     | Type         | Description                                 |
| --------- | ------------ | ------------------------------------------- |
| `enabled` | boolean      | `true` if relayer private key is configured |
| `address` | string\|null | Relayer TRON address, or `null` if disabled |

***

#### `POST /api/relayer/withdraw`

Submit a withdrawal via relayer. The relayer verifies the ZK proof off-chain, then signs and broadcasts the transaction. Recipient receives `denomination - 4 USDT`.

**Rate limited**: 5 requests per minute.

```bash
curl -X POST https://api.8mix.gg/api/relayer/withdraw \
  -H "Content-Type: application/json" \
  -d '{
    "denomination": "100",
    "proof": "0x...",
    "root": "0x...",
    "nullifierHash": "0x...",
    "recipient": "TRecipient..."
  }'
```

| Body field      | Type   | Required | Format                               |
| --------------- | ------ | -------- | ------------------------------------ |
| `denomination`  | string | yes      | `100`, `1000`, or `3000`             |
| `proof`         | string | yes      | `0x` + 512 hex chars (Groth16 proof) |
| `root`          | string | yes      | bytes32 hex                          |
| `nullifierHash` | string | yes      | bytes32 hex                          |
| `recipient`     | string | yes      | TRON address (who receives USDT)     |

**Success:**

```json
{ "ok": true, "data": { "txid": "abc123..." } }
```

**Errors:**

| HTTP | Error                                      | Meaning                                   |
| ---- | ------------------------------------------ | ----------------------------------------- |
| 400  | `Invalid proof`                            | Proof format invalid                      |
| 400  | `Invalid ZK proof`                         | Proof failed off-chain verification       |
| 400  | `Proof verification failed`                | snarkjs rejected the proof                |
| 400  | `Root expired — regenerate proof`          | Merkle root no longer in contract history |
| 409  | `Note already spent`                       | Nullifier was already used                |
| 429  | `Relay rate limit — try again in a minute` | Rate limit exceeded                       |
| 503  | `Relayer not available`                    | Relayer private key not configured        |

**Security**: The relayer verifies the ZK proof off-chain (`snarkjs.groth16.verify`) before spending gas on an on-chain transaction. Duplicate requests with the same nullifier are blocked by an in-memory mutex.

***

### Health

#### `GET /api/health`

```bash
curl https://api.8mix.gg/api/health
```

```json
{
  "ok": true,
  "data": { "status": "ok", "timestamp": 1774172180889 }
}
```

***

### Error Reference

All errors follow the same format:

```json
{ "ok": false, "error": "Error description" }
```

| HTTP | Error                                        | When                                |
| ---- | -------------------------------------------- | ----------------------------------- |
| 400  | `Invalid denomination. Use: 100, 1000, 3000` | Unknown denomination                |
| 400  | `Invalid TRON address`                       | Malformed base58 address            |
| 400  | `Invalid nullifierHash (bytes32 hex)`        | Not `0x` + 64 hex                   |
| 400  | `Invalid commitment (bytes32 hex)`           | Not `0x` + 64 hex                   |
| 400  | `Invalid proof (hex string expected)`        | Not `0x` + 512 hex                  |
| 400  | `Invalid JSON body`                          | Malformed request body              |
| 400  | `Invalid ZK proof`                           | Proof failed off-chain verification |
| 400  | `Root expired — regenerate proof`            | Merkle root expired from history    |
| 404  | `Pool not configured`                        | Pool missing in server config       |
| 404  | `Not found`                                  | Unknown route                       |
| 409  | `Note already spent`                         | Nullifier already used              |
| 413  | `Request body too large`                     | Body exceeds 10KB                   |
| 429  | `Too many requests, try again later`         | Global rate limit exceeded          |
| 429  | `Relay rate limit — try again in a minute`   | Relayer rate limit exceeded         |
| 500  | `Internal server error`                      | Unhandled error (production)        |
| 503  | `Relayer not available`                      | Relayer not configured              |

***

### Integration Guide

#### Deposit Flow

```
1. GET  /api/pools/100                    → get pool info, verify it exists
2. GET  /api/balance/{userAddr}           → check user USDT balance
3. GET  /api/allowance/{userAddr}/100     → check current allowance
4. POST /api/tx/approve                   → build approve tx (if allowance insufficient)
   → sign with user wallet & broadcast to TRON
   → wait for on-chain confirmation
5. POST /api/tx/deposit                   → build deposit tx
   → sign with user wallet & broadcast to TRON
   → wait for on-chain confirmation
6. Save the note: 8mix-100-{nullifierHex}{secretHex}
```

**Note format**: `8mix-{denomination}-{nullifierHex}{secretHex}` where nullifier and secret are each 62 hex characters (31 bytes from `crypto.getRandomValues`). The commitment is `Poseidon(nullifier, secret)`.

#### Withdrawal via Relayer (gasless)

```
1. Parse note → extract nullifier, secret, denomination
2. POST /api/pools/{denom}/check-spent    → verify note not spent
3. GET  /api/pools/{denom}/root           → get current Merkle root
4. POST /api/pools/{denom}/can-withdraw   → verify root + nullifier are valid
5. Fetch deposit events from TronGrid     → build Merkle tree
6. Generate ZK proof client-side          → snarkjs.groth16.fullProve(input, wasm, zkey)
7. POST /api/relayer/withdraw             → submit proof to relayer
   → relayer verifies proof off-chain
   → relayer signs & broadcasts tx
   → returns txid
8. Recipient receives denomination - 4 USDT
```

#### Self-Withdrawal (user pays gas)

```
1–6. Same as relayer flow
7. POST /api/tx/withdraw                  → build unsigned withdraw tx
   → sign with user wallet & broadcast to TRON
   → user pays energy/bandwidth costs
8. Recipient receives full denomination (no fee if relayer = zero address)
```

***

### Smart Contracts

| Contract            | Address                              | Network      |
| ------------------- | ------------------------------------ | ------------ |
| EightMix 100 USDT   | `TEd3WceUmLB25bqygmk6zgZjhAYnSryTNc` | TRON Mainnet |
| EightMix 1,000 USDT | `TCY3LAEtVKicxaP1jDC47KSVvykeB2nwhX` | TRON Mainnet |
| EightMix 3,000 USDT | `TVSY8gM1tGSBgzT3naiRmRAbCmyn6ZUdjA` | TRON Mainnet |
| USDT (TRC-20)       | `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` | TRON Mainnet |
| Relayer             | `TAWHckA571Cu3h8njup8Bv5oqXGdtwuX2T` | TRON Mainnet |

***

### Self-Hosting

#### Installation

```bash
git clone <repo>
cd api
cp .env.example .env
nano .env
npm install
npm run pm2
```

#### Required Environment Variables

```
PORT=3001
CORS_ORIGIN=https://your-frontend.com
TRONGRID_API=https://api.trongrid.io
TRONGRID_API_KEY=your_trongrid_key
POOL_100=TEd3WceUmLB25bqygmk6zgZjhAYnSryTNc
POOL_1000=TCY3LAEtVKicxaP1jDC47KSVvykeB2nwhX
POOL_3000=TVSY8gM1tGSBgzT3naiRmRAbCmyn6ZUdjA
RELAYER_PRIVATE_KEY=your_private_key_hex
```

#### Nginx Reverse Proxy

```nginx
server {
    listen 443 ssl;
    server_name api.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
    }
}
```

#### PM2 Commands

```bash
npm run pm2          # start
npm run pm2:logs     # view logs
npm run pm2:stop     # stop
```

***

> **8mix** — <https://8mix.gg>
