Skip to main content
WaaS gives each of your customers a hierarchical-deterministic (HD) wallet with derived addresses across BTC, BCH, LTC, ETH, BSC, POL, TRX, SOL, XRP, BASE, ARB, AVAX and CELO, balances, transfers, and 24/7 deposit monitoring. Authenticate with your Payments API key (X-API-Key).
Base, Arbitrum, Avalanche and Celo are newly supported. They work the same way as the other EVM chains (native coin + USDC/USDT), but have had less production traffic than ETH/BSC/POL — test with small amounts first.
Base URL: https://api.rach.finance/api/v1/ · Auth: X-API-Key

What you can hold on each chain

GET /wallet/assets returns the exact asset list configured for your current environment — read it at runtime rather than hard-coding this table. It also returns transfers_enabled, which is false on a test key. Numbers in brackets are the asset’s decimals.
Two traps worth reading twice:
  • USDT and USDC on BSC have 18 decimals, not the 6 they have everywhere else. A base-unit amount copied from an ETH integration is off by a factor of a trillion.
  • There is no USDC on TRON. TRON carries USDT only; asking for USDC there is refused.
BASE, ARB, AVAX, CELO and XRP have no sandbox. They exist on live keys only, so build those flows against a live key and small amounts. A test key asking for them is refused with … is not configured for this environment.
Network names are normalised, so ETHEREUM, POLYGON, MATIC, BNB, TRON, SOLANA, BITCOIN, LITECOIN, RIPPLE, ARBITRUM and AVALANCHE all resolve to the codes above.

1. Create a customer wallet

One wallet per customer (idempotent per customer_id).
Response
The mnemonic is returned once at creation. It is the customer’s recovery phrase — store it securely (or hand it to the customer) and never log it.

2. Derive a deposit address

Create a receive address on a specific chain. Set enable_monitoring: true to have Rach watch it for deposits and fire webhooks.
Response
network is one of BTC BCH LTC ETH BSC POL TRX SOL XRP BASE ARB AVAX CELO. Share address with your customer to receive funds.

3. Read balances

Balances are served from Rach’s database, which our monitor keeps in step with the chain (raising it on deposits, lowering it on spends). Reading them is free and instant, so you can poll this as often as your UI needs.
Response
Each address returns one entry per supported currency on that network, including zeros. as_of is when we last read that figure from the chain, and source is database or chain.

Refreshing

?refresh=true does not block on a chain read — it returns right away and schedules the refresh, replying with refresh_requested: true and refresh_eta_seconds. Poll again after that interval to see the updated figure. Many concurrent refresh requests collapse into a single chain read, so a “pull to refresh” button is safe to wire directly to it.
Use ?live=true only when you genuinely need the chain read this instant — for example the confirmed-vs-unconfirmed split on BTC/LTC/BCH, which only the live path returns. It is throttled per address, so it is not a substitute for normal polling.

Spendable vs balance

spendable is what can actually be sent right now, and it is not always equal to balance:
  • XRP — spendable excludes the locked ~1 XRP base reserve.
  • BTC / LTC / BCH — with ?live=true you also get confirmed; balance may include unconfirmed (0-conf) funds that cannot be spent yet.
  • Everything else — spendable equals balance.

4. Quote a transfer

POST /wallet/{customer_id}/transfer-quote takes the same body as the transfer and returns what would happen, without sending anything. Use it to show your customer the real delivered figure before they confirm.
Response
Quote amounts come back in base units (unit: "base"), whatever unit you sent — divide by 10^decimals to display them. recipient_amount is gross_amount minus your WaaS fee; the TRON sponsorship fee below is applied separately at settlement.
Pass the returned fee_quote_hash back on the transfer and the send is refused with 409 and code: "quote_changed" if your fee configuration moved in between. Omit it and the transfer proceeds at whatever the fee is when it lands.

5. Send crypto out

amount is a string; unit is decimal (whole coins, default) or base (wei/satoshi).
Idempotency-Key is required, 16–128 characters, and a transfer without one is rejected with 400. Generate one per transfer attempt (a UUID is fine) and reuse the same value when you retry — that is what stops a timeout from sending the money twice.
Response
XRP transfers accept a destination_tag. For chains that need it, gas/fees are handled by Rach and reported back in fee_amount / gas_fee.
amount is what leaves the wallet, not always what the recipient receives. Two things can be deducted from it before it lands: your own WaaS fee if you have configured one, and the TRON sponsorship fee below. Always read the amount field in the response — that is the figure actually sent — rather than echoing back what you requested.
Transfers require a live key. A test_sk_ key is refused with 400 — there is no sandbox for sending. You can create wallets, derive addresses and read balances on testnet, but the send path is live-only. GET /wallet/assets reports this as transfers_enabled.

6. Check a transfer’s status

A transfer’s HTTP response tells you it was submitted, not that it confirmed. Read the outcome back with the Idempotency-Key you sent:
Response
This is the endpoint to poll after a timeout. If your transfer call never returned, replay it with the same Idempotency-Key — you will get the original result rather than a second transfer — or read it here.

WaaS fee collection

You can charge your own fee on every customer transfer. Rach deducts it from the transfer amount and sends it on-chain directly to your address — Rach never holds it.
No address for a network means no fee is collected on it. Fees are only taken where you have supplied an address, so add one per network you want to earn on. PUT /wallet/fees/addresses updates addresses alone; sending "" for a network removes it and stops collection there.
USD figures (fee_cap_usd, flat_fee_usd) are converted to the asset at the current market rate; stablecoins convert 1:1. On UTXO chains (BTC/LTC/BCH) the fee is an extra output in the same transaction, so fee_tx_hash is empty. On every other chain it is a second transaction. Read your earnings with GET /wallet/earnings.

Sending USDT on TRON without TRX

TRON does not work like the EVM chains, and this trips up nearly every integration:
  1. A TRON address does not exist on-chain until it receives something. An unactivated address cannot send anything at all, no matter how much USDT it holds.
  2. A USDT (TRC-20) transfer costs ENERGY. Without it, the sender burns TRX — often 13–30 TRX per transfer. A customer holding only USDT is otherwise stuck.
Rach handles both automatically. When your customer sends USDT on TRON, we activate their address if needed and cover the energy, so the transfer works with zero TRX in the wallet. You do not need to pre-fund customers with TRX.
A sponsorship fee of 1 USDT is deducted from the transfer amount. If your customer sends 100 USDT, the recipient receives 99 USDT and 1 USDT is taken to cover the network cost Rach paid on their behalf.
  • It is taken out of the amount, not charged separately — so a customer whose entire balance is USDT can always transfer.
  • It is only charged after the transfer succeeds.
  • It is skipped when the transfer is too small to carry it.
  • It applies only to USDT on TRON, and only when we actually sponsored the transfer. A customer with their own energy pays nothing.
Surface this to your users before they confirm a TRON USDT transfer, and read the response’s amount field for the true delivered figure.
This is a platform-level fee for TRON network costs. It is separate from — and additional to — any WaaS fee you configure for yourself.

More endpoints

Full schemas are in the Payments API Reference under WaaS. Deposits are detected automatically — see 24/7 Monitoring.