TOR Coin

Developer API

Sell TOR Coin inside your own wallet or platform. You embed one snippet, we host the checkout, the Rust vault and the payout rails — and your payment credentials are never exposed to your users or to your front-end.

Your snippet

<script src="https://www.torcoin.fun/docs/embed.js" data-tor-key="pk_live_your_key"></script>
<button data-tor-buy="2.00">BUY TOR</button>

Replace pk_live_your_key with the publishable key issued for your integration. The secret key (sk_live_…) stays on your server and is only used for server-to-server calls — it must never be shipped to a browser.

Your API keys

Sign in on the platform page first: a key is issued against your TOR wallet key pair, so the wallet that owns the integration is provable.

Full reference

# TOR Coin — Developer API

Sell (and buy back) TOR Coin inside your own wallet, storefront or bot. You embed
one script tag, the widget opens a **BUY TOR** popup on your page, TOR Coin hosts
the checkout, the Rust vault signs the TOR delivery and the payout rails — your
payment credentials are never exposed to your users or your front-end.

Base URL: **`https://www.torcoin.fun`** (the domain the platform is served on;
local dev only: `http://127.0.0.1:5000`). Every URL the platform hands out — the
payment return/cancel URLs, the partner snippet, the CORS allow-list — is built
from `TOR_BASE_URL`, which defaults to that domain.

The TOR itself lives on the OneNess chain behind two hosts that serve **one**
ledger:

| role | URL | used for |
|---|---|---|
| node 1 (primary) | `https://www.torblockchain.fun` | every read, every transfer, the TOR/CAD market price |
| node 2 (failover) | `https://torblockchain.onrender.com` | only when node 1 does not answer (same `chain_id` 072316, same head) |

```text
TOR_CHAIN_RPC_URL=https://www.torblockchain.fun
TOR_CHAIN_RPC_FALLBACK_URL=https://torblockchain.onrender.com
```

A failover is never silent: `/healthz`, `GET /api/vault` and the readiness gate
report which host answered (`chain_endpoint`, `chain_failover`).

---

## 1. Get a key

API keys are **bound to your TOR wallet key pair**, so the wallet that created
the key is the wallet the integration belongs to.

```bash
# 1. sign in — this returns your TOR address + public key (session cookie set)
curl -c cookies.txt -X POST http://127.0.0.1:5000/api/session \
  -H 'content-type: application/json' \
  -d '{"email":"dev@your-app.com"}'

# 2. issue a key pair with your own fees (basis points, 0-2000 = 0-20%)
curl -b cookies.txt -X POST http://127.0.0.1:5000/api/keys \
  -H 'content-type: application/json' \
  -d '{"name":"My Wallet","fee_bps_buy":150,"fee_bps_sell":150}'
```

The response carries `partner.pk_key` (public, safe in HTML) and
`partner.sk_key` (secret, server-side only, **shown once**), plus a ready-to-paste
`snippet`.

`GET /api/keys` lists the keys issued to the signed-in wallet (secret masked).

| field | meaning |
| --- | --- |
| `pk_key` | `pk_live_…` — identifies your integration in the browser |
| `sk_key` | `sk_live_…` — server-to-server only, never ship it to a browser |
| `wallet_address` | the TOR wallet the key belongs to |
| `fee_bps_buy` / `fee_bps_sell` | your transaction fee, in basis points |

---

## 2. Embed the widget (the BUY TOR button + popup)

Paste this where you want the button. `data-tor-key` is your publishable key.

```html
<script src="https://www.torcoin.fun/embed.js"
        data-tor-key="pk_live_your_key"></script>

<button data-tor-buy="2.00">BUY TOR</button>
<button data-tor-sell="1.00">SELL TOR</button>
```

* `data-tor-buy="2.00"` — the buyer pays **2.00 CAD**; the popup shows the live
  quote (TOR/CAD), the TOR they receive and the fee before they confirm.
* `data-tor-sell="1.00"` — the seller hands back **1.00 CAD** worth of TOR and
  gets the CAD pushed to their payout destination.
* Optional: `data-tor-theme="dark"`, `data-tor-fee-bps-buy` / `data-tor-fee-bps-sell`
  (informational — the server always charges the fee stored against your key), and
  `data-tor-address="TOR…"` to pre-fill the receiving wallet.

Any element with `data-tor-buy` / `data-tor-sell` opens the popup, so you can style
your own buttons:

```html
<button class="buy-tor" data-tor-buy="5.00">Buy 5 CAD of TOR</button>
```

Programmatic control (no button markup):

```js
// window.TorCoin is installed by embed.js (alias of window.TorCoinWidget)
TorCoin.buy({ amountCad: 2.00 });      // the same popup, BUY pane
TorCoin.sell({ amountTor: 0.198568 }); // the same popup, SELL pane
TorCoin.open({ side: "buy", amountCad: 2.00 });
TorCoin.close();                       // close the popup yourself
TorCoin.baseUrl;                       // "https://www.torcoin.fun"
TorCoin.publicKey;                     // the data-tor-key this page loaded with
```

Both `sell({ amountCad })` (what the seller gets in CAD) and
`sell({ amountTor })` (what they hand back in TOR) are accepted; the pane shows the
converted side before anything is signed.

The popup is a modal over your page: it creates the order on our side
(`POST /api/order`), opens the hosted checkout for the card / approval step, and
polls `GET /api/order/<id>` until the TOR lands in the wallet — it then shows the
receipt id, the TOR balance and the block reference.

---

## 3. Fees, collected in TOR

A fee is `basis points` of the CAD amount, set per key (0-2000 = 0-20%):

* **Buy** — the fee is taken off the top, so the buyer receives
  `(amount_cad - fee_cad) / price` TOR. The TOR-equivalent `fee_tor` is booked
  **in TOR** into the platform fee wallet (`TOR_FEE_WALLET`) inside the vault
  ledger (`vault.collectFee`, entry kind `COLLECT_FEE`), so it is real TOR that can
  be sold later — not a number in a spreadsheet.
* **Sell** — the fee is withheld from the CAD payout; the matching `fee_tor` is
  recorded on the same sale (the TOR itself is already in the vault reserve).

The quote always reports both `fee_cad` and `fee_tor`, so your UI can show the fee
in whichever unit your user thinks in.

```json
{ "quote": { "side": "buy", "charge_minor": 200, "charges_cad": "2.00",
  "fee_bps": 150, "fee_cad": "0.03", "fee_tor": "0.005957",
  "tor_amount": "0.391180", "price_cad": "5.036040" } }
```

---

## 4. Server-to-server calls

Every endpoint accepts your publishable key in the `X-Tor-Key` header (the widget
sends it for you) or `?key=pk_live_…`. The secret key is only needed for key
management and must stay on your server.

### Quote

```bash
curl -X POST http://127.0.0.1:5000/api/quote \
  -H 'content-type: application/json' -H 'X-Tor-Key: pk_live_your_key' \
  -d '{"side":"buy","amount_cad":2.00}'
```

### Buy (create the order)

```bash
curl -X POST http://127.0.0.1:5000/api/order \
  -H 'content-type: application/json' -H 'X-Tor-Key: pk_live_your_key' \
  -H 'Idempotency-Key: order-1234' \
  -d '{"amount_cad":2.00,"address":"TOR...","email":"buyer@example.com"}'
```

Response: the order (`id`, `tor_amount`, `charge_display`, `status`) and
`payment.approve_url` — send the buyer there, or let the widget do it.

Status values: `payment_pending` -> `funded` (TOR delivered, `receipt_id` + `txid`
set) -> or `payment_failed` / `expired`.

### Sell

```bash
curl -X POST http://127.0.0.1:5000/api/sell \
  -H 'content-type: application/json' -H 'X-Tor-Key: pk_live_your_key' \
  -d '{"amount_tor":"0.198568","payout_email":"seller@example.com","address":"TOR..."}'
```

The vault takes the TOR into escrow, then the payout provider pushes CAD to the
seller; `status` becomes `payout_settled` with a `provider_ref`.

### Read back

| method | path | purpose |
| --- | --- | --- |
| `GET` | `/api/config` | currency, fees, price, widget settings |
| `GET` | `/api/rate` | live TOR/CAD quote + freshness + source |
| `GET` | `/api/rate/history` | the chart series |
| `POST` | `/api/session` | sign in and get your TOR wallet |
| `GET` | `/api/wallet` | your wallet address + balance |
| `POST` | `/api/quote` | price a buy or a sell |
| `POST` | `/api/order` | create a buy order |
| `GET` | `/api/order/<id>` | order status + on-chain balance |
| `POST` | `/api/order/<id>/redirect` | re-open the hosted approval link |
| `POST` | `/api/sell` | sell TOR back for CAD |
| `GET`/`POST` | `/api/keys` | list / create your API keys |
| `GET` | `/api/vault` | the vault summary (reserve, supply, conservation) |
| `GET` | `/healthz` | platform + rate + vault status (no secrets) |

---

## 5. Errors

Errors are `{"ok": false, "error": {"code": "...", "message": "..."}}` with a
matching HTTP status.

| code | status | meaning |
| --- | --- | --- |
| `SIGN_IN_REQUIRED` | 401 | call `POST /api/session` before `/api/keys` |
| `BAD_FEE` | 400 | fees must be integers between 0 and 2000 bps |
| `AMOUNT_TOO_SMALL` / `AMOUNT_TOO_LARGE` | 400 | outside the order bounds |
| `MISSING_PAYOUT_DESTINATION` | 400 | a sell needs a payout email / card |
| `INSUFFICIENT_BALANCE` | 409 | the wallet holds less TOR than the sale needs |
| `PAYMENT_NOT_COMPLETED` | 402 | the card was not captured |
| `CHAIN_NOT_PRODUCTION` | 409 | the vault is not bound to a production chain — real money is refused |
| `INSUFFICIENT_RESERVE` | 400 | the vault reserve cannot cover the TOR (or the fee) |

`CHAIN_NOT_PRODUCTION` is deliberate: the platform will not take a real card
payment while the TOR it would deliver exists only in a mock ledger. The vault
reports `torchain` when it is bound to the running OneNess node, and that mode is
one of `server.config.PRODUCTION_CHAIN_MODES` — the same set the readiness gate
reads, so a genuine node is never refused and a simulator is never accepted.
Check `GET /healthz` -> `vault_up` and the readiness gate
(`python tools/live_readiness.py`).

One caveat the gate keeps flagging until the address bridge exists: the node pays
only `TOR1<base32>.onion`, so a credit to a storefront wallet
(`TOR<40 hex><checksum>`) is a **vault-ledger balance backed by the reserve**, not
an on-chain transfer. Each such order is recorded in the platform log as
`wallet.off_chain`, with that sentence attached.

---

## 6. Verify a delivery yourself

Nothing in this API asks you to trust a screen. After a buy:

```bash
curl -s http://127.0.0.1:7788/health          # chain mode, height, price
curl -s -X POST http://127.0.0.1:7788/rpc \
  -H 'content-type: application/json' \
  -H "x-tor-vault-token: $TOR_VAULT_RPC_TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"vault.balance","params":{"address":"TOR..."}}'
```

* `vault.verifyReceipt` re-hashes the receipt.
* `vault.verifyLedger` re-walks the whole hash-chained ledger and proves
  `reserve + balances + escrow == minted`.
* The order's `txid` is the node's own transaction id when the vault is bound to
  the OneNess chain (`TOR_CHAIN_KIND=torchain`).

Raw file: docs/DEV_API.md in the project root.