Skip to main content
Rach CaaS is a global money-movement rail. Give your customers non-custodial USDC / USDT wallets, then fund them from local currency, send to anyone in the world by phone number, and cash out back to local fiat — all over WhatsApp, SMS, USSD or email, so it works on a smartphone or a basic feature phone. It’s flexible: use the full fiat → stablecoin → fiat money-movement flow, or treat it as pure stablecoin infrastructure (non-custodial USDC/USDT wallets + on-chain transfers). The standard CaaS rail uses ERC-4337 v0.7 smart-contract wallets keyed by phone number, settling on Polygon PoS, Base or BNB Smart Chain — you pick per request. Two parallel native rails derive durable addresses from the same customer identity: TRON USDT (T...) and Solana USDC/USDT.
Choose the EVM settlement chain per request with the optional chain field on transfers, swaps, deposits, withdrawals and balance reads. Omit it and you get the deployment’s primary chain, which is what every existing integration already does — so adding multi-chain support changes nothing you have already built.Native TRON USDT and Solana SPL are separate parallel rails rather than EVM profiles, so they have their own /v1/tron/* and /v1/solana/* endpoints instead of a chain value — their wallets are not EVM addresses.

Choosing a network

One field. Your integration is otherwise identical across every EVM chain.
A customer has one wallet address across every EVM chain — the same 0x… on Polygon, Base and BSC — because the account factory sits at the same address on each. You do not provision per chain.
A chain that isn’t configured for your deployment is rejected, not quietly settled somewhere else. A live request naming an unavailable chain returns an error listing the chains you can use; sandbox accepts any known network so you can build against a chain before it goes live.
Amounts are always in the token’s own precision. USDC and USDT are 6 decimals on Polygon and Base but 18 on BNB Smart Chain — read balance_usdc / balance_usdt as returned rather than assuming six.
Solana USDC & USDT availability: customers hold zero SOL. Sponsorship on Solana is native rather than contract-based — every transaction names a fee payer, and Rach is named as that payer, so the customer signs only as token authority. GET /health/ready returning solana: INBOUND_READY means provisioning and balance reads passed and Solana receipt monitoring is configured while sponsored sends remain deliberately held. It does not certify the separately operated scanner worker or webhook delivery; use a confirmed deposit record or signed transfer.received webhook as proof of an observed credit. With monitoring disabled, Solana reports DEGRADED even where balance reads work. solana: READY permits POST /v1/solana/transfers/send.Before a Solana provision response succeeds, Rach creates both the customer’s USDC and USDT Associated Token Accounts. That makes an otherwise zero-SOL owner address ready for exchanges that require a recipient token account. If an outbound recipient has never held the mint, Rach also pays the separate one-off rent to open their token account; that cost is not recoverable, so every transfer reports created_recipient_ata telling you whether it applied. See Solana USDC & USDT.
TRON USDT availability: customers can hold zero TRX. GET /health/ready returning tron: INBOUND_READY means the native node read-side and reader-account checks passed and the rail is receive-only; it does not certify the separately deployed custody or deposit-scanner worker. Read balances through balanceOf, and treat a confirmed dashboard deposit record or a signed transfer.received webhook as proof that an inbound USDT transfer was observed. tron: READY permits POST /v1/tron/transfers/send after a live sponsor-capacity preflight.
CaaS is a separate product with its own base URL and its own API key. Base URL https://api.rach.finance/caas/v1/; B2B keys are prefixed rach_sk_test_ / rach_sk_live_ and sent in X-API-Key. A Payments live_sk_/test_sk_ key will not work here. See Get your API keys.

The mental model

The single most important idea: a wallet belongs to the person, not the business that created it. A wallet is keyed by phone number and holds real, non-custodial USDC on Polygon. What each business keeps privately is its own USDC ledger — the balance it pre-funds with Rach and draws down when acting for its customers.

Money-in is open

Anyone can fund a wallet or send to a phone number — even one that has never been registered. The cost is always paid from the sender’s own ledger.

Money-out is gated

Only a member of a wallet may originate a transfer, withdraw to cash, or change its phone number. Membership starts at provisioning and grows only with the customer’s consent.

Identity is portable

The address is derived from a permanent identity salt, not the phone number. Change the SIM, keep the wallet, balance and history intact.

Reach

Rach meets customers on whatever channel they have — WhatsApp, SMS, USSD (where supported) and email. Every notification and self-service flow works the same whether the customer has a smartphone or a basic feature phone, so no one is left unable to claim or move their money.

Fees & gas sponsorship

On the ERC-4337 EVM rail, Rach charges 0.30% per transfer, capped at 1.20 USDC/USDT, deducted from the transaction amount so there is nothing separate to reconcile — the sender is debited exactly what they asked to send. That one fee covers the entire gasless experience:

Gas is on us

CaaS sponsors the gas for every operation on behalf of your customers. They hold and move stablecoins with no native gas token — nothing to top up, nothing to manage.

Dedicated bundler

UserOperations are submitted through Rundler — CaaS’s own dedicated ERC-4337 bundler that packages and lands them on-chain reliably.

Address monitoring

Every wallet is watched by dedicated, CaaS-powered address monitoring, so incoming deposits and transfers are detected and fired to your webhooks around the clock.
For native TRON USDT, Rach sponsors the network costs (account activation, Energy and Bandwidth) when sponsored sends are enabled; customers never need to keep TRX in their wallet. TRON business-fee collection is deliberately not inferred from an EVM fee address: it remains off until Rach configures an explicit native TRON collection policy, so no hidden TRON fee is deducted during the sponsored-send rollout.

Solana rail

Sponsorship on Solana needs no paymaster and no resource lease. Every transaction names a fee payer; Rach is named as that payer, so your customer signs only as token authority and never holds SOL. Provisioning is synchronous from the integrator’s perspective: Rach finalizes the customer’s USDC and USDT token accounts before returning token_accounts_ready: true. Do not expose an address from a failed/503 provision response; retry the same request until it succeeds.
recipient_address must be a normal wallet address. Rach rejects off-curve addresses — program-derived accounts that can hold tokens but cannot sign — because funds sent to one may be permanently unrecoverable. There is no equivalent hazard on the EVM or TRON rails.
Only SETTLED and FAILED are terminal. SUBMISSION_UNKNOWN means the outcome is being reconciled against the recorded transaction signature — never retry it with a new idempotency key, because the transfer may already have landed. Merchant views live under /v1/dashboard/solana/* (status, overview, wallets, transfers, deposits) and mirror the TRON dashboard exactly. For the full B2B flow—provisioning, balances, receipt webhooks, idempotency and sponsored withdrawals—see Solana USDC & USDT.

TRON dashboard and platform operations

Merchant dashboards can show the same operational facts without exposing custody or sponsor secrets. With the normal CaaS dashboard JWT, use:
  • GET /v1/dashboard/tron/status for the receive-only / sponsored-send state;
  • GET /v1/dashboard/tron/overview, /wallets, /transfers, and /deposits for tenant-scoped activity;
  • GET /v1/dashboard/tron/wallets/{blind_index} for a customer’s live USDT balanceOf.
Rach’s own operational tooling for sponsor readiness and reconciliation is read-only: it cannot stake, delegate, enable sends, or sign a transfer on your behalf. If the rail is disabled, the dashboard endpoints above return 503 TRON rail is not configured rather than silently appearing empty.
Swaps are priced separately from transfers, and cost more. A swap crosses an on-chain liquidity pool, and that has its own unavoidable cost. Rach’s share is capped; the rest is not. The 0.30%/1.20 transfer rate does not apply here.On a 1,000 USDC swap that is roughly 7.50 all-in, not 1.50. On a 100 USDC swap it is about 1.00. Two of the three components are uncapped percentages, so the absolute cost scales with the size of the swap.You never have to infer this: every swap response returns a cost_breakdown with all three components and total_expected. Read it before you show a number to your user.
Transfers, funding and withdrawals are the simple case — 0.30% capped at 1.20, and nothing else.

Earn on every transfer

You can charge your own fee alongside Rach’s and keep all of it. Rach cut its transfer margin from 0.4%/1.50 to 0.30%/1.20 specifically to leave you room to price your product without the combined cost pushing your customers away. Set it once on your dashboard, or through the API: You price the same way Rach does, in one of two modes. Percentage with a maximum — a rate, bounded so a large transfer cannot produce an absurd fee. This is the same shape as Rach’s own 0.30% capped at 1.20.
That charges 0.25%, never more than 1.00. Set fee_cap_micros: 0 for uncapped. Flat — one fixed charge per transfer, whatever its size.
That charges 0.20 on every transfer.
The two modes are alternatives, never combined. A percentage is bounded by its cap; it is not added to the flat amount. Whichever mode you set is the only one that charges.

You are paid on-chain, not by Rach

Your fee moves directly from the sender to your address, inside the same atomic on-chain batch as the transfer itself. Rach never takes custody of it. There is no payout to request, no minimum to reach, and no balance to reconcile — the fee either settled with the transfer or it did not happen.
Both fees come out of the amount sent. On a 100 USDC transfer with PERCENTAGE at 0.25%, the sender is debited exactly 100 — of which 0.30 goes to Rach, 0.25 to you, and the recipient receives 99.45.
On a transfer too small to carry both fees, your fee is waived rather than leaving the recipient with nothing. You earn nothing on that transfer and it still settles. Rach’s own margin is never waived — a transfer too small to cover even that is rejected outright rather than delivering zero.
Read what you have earned at any time:
Earnings are the amounts actually charged at the time of each transfer, not a recalculation from your current rates — so changing your pricing never restates what you already made.
The 10% ceiling on fee_bps is a typo guard, not a pricing policy. Basis points invite an order-of-magnitude slip — typing 500 meaning 0.5% — and that mistake is charged to your own customers at full value on every transfer until somebody notices.

The flow, end to end

1

Pre-fund your ledger (once)

Top up your USDC ledger with Rach from your dashboard — request a locked rate, wire the local currency, Rach confirms receipt and credits your spendable balance.
2

Provision the customer

Create their wallet by phone number (offline, gasless).
3

Fund the wallet

Move USDC/USDT from your ledger to the customer wallet on-chain.
4

Send or cash out

Send to any phone worldwide, or off-ramp back to local fiat.
Why the ledger and the wallet are separate: your ledger is keyed to your business and the wallet is keyed to the person. Funding a customer who also uses another Rach partner simply debits your balance and tops up the shared wallet — the other business is unaffected.

Provision a user

Provisioning derives the smart-contract wallet address offline (no gas, no on-chain transaction) and records you as the wallet’s first member.
Response
The status tells you what you can do next:

Read balances

Response

Fund a customer (on-ramp)

Move stablecoins from your ledger to a customer wallet. Rach atomically debits your ledger before touching the chain — insufficient balance is refused cleanly with no on-chain effect — then settles as a gasless UserOperation.

Send to any phone (or wallet)

Send USDC/USDT to any phone number in the world — instant and final on-chain within seconds. The recipient does not need to be your customer, or provisioned at all: if the phone has never been seen, Rach creates a wallet on the fly and notifies the recipient on their channel (WhatsApp / SMS / USSD / email) with how to claim and cash out — no app required. sender_phone and recipient_phone each accept either an E.164 phone number (+2250700000001) or a 0x SCW wallet address — the format is auto-detected. Convert fiat first with an FX quote if needed, and always pass an idempotency_key.
Money-in is open, money-out is gated. The sender must be a member of a wallet you operate — knowing a phone number is never enough to move someone’s money (it’s rejected with a clear permission error). The recipient can be anyone. If the recipient belongs to another business, that business receives a transfer.received webhook; if they’re unregistered, Rach notifies them directly to claim.

FX conversion

Quote fiat → stablecoin (locked for a short window) before you fund or send.
Response
Rates are per-currency (XOF, XAF, NGN, GHS, …) — USDC is the settlement rail underneath.

Cash out (off-ramp)

Two routes get money back to a local bank or mobile-money account. Business-initiated — you off-ramp your own customer:
Rach sweeps the stablecoin on-chain and disburses local currency to the destination.

Paying to a bank account

Set destination_type to BANK_ACCOUNT and supply the account instead of a mobile number:
Send account_number in whatever format the destination market uses — IBAN for XOF, XAF and Europe, NUBAN for Nigeria, and so on. Rach passes it to the payout provider unmodified rather than trying to parse it, so a new market needs no change on your side.
account_name is required for bank payouts. A mismatched name is the most common reason a bank returns a transfer, and a returned payout is slower to unwind than a rejected one.

When you don’t know the recipient’s account

This is the remittance case: your customer knows a phone number and nothing else about who they’re paying. Set use_saved_destination and omit the account details entirely:
Rach pays to the recipient’s own saved default for that currency. The recipient supplies those details themselves — over WhatsApp, SMS or USSD when they claim — so you never have to collect someone else’s bank account, and the details come from the person who actually owns them. If they have no saved destination for that currency, the call is rejected before anything moves on-chain, so a withdrawal can never sweep funds it has nowhere to send.
Passing save_destination: true alongside explicit details stores them against the recipient, so later payouts to the same person can use use_saved_destination and skip the account fields.
Self-service — someone who received money without belonging to any business cashes out through Rach directly over their channel, after a one-time identity check. No API call on your side.
Compliance gate. Receiving money never requires identity — anyone can be paid — but no payout leaves the system until Rach has screened the recipient against AML and sanctions checks.

Swap USDC ⇄ USDT

One call swaps one stablecoin for the other on the customer’s wallet. Under the hood a dedicated CaaS swap smart contract talks to multiple on-chain DEXes, finds the best swap route available at that moment, protects against slippage, and executes the whole swap in a single gasless command — you never touch a router, fetch a quote, or pay gas yourself.
Response
Returns 202 with a swap_id. The swap settles asynchronously on-chain — poll GET /caas/v1/swaps/{id} or subscribe to the swap.settled / swap.failed webhooks for the terminal result and on-chain tx hash. Three fields matter when you show this to a user:
  • cost_breakdown.total_expected — the all-in cost, covering Rach’s fee, the swap contract, and the pool. This is the number to display, not fee.
  • expected_out — what the customer should receive at the quoted market price.
  • min_out — the floor. If the market moves against the swap before it lands so that output would fall below this, the swap reverts on-chain and fails rather than filling at a worse price. The tolerance is max_slippage_bps (0.5% by default).
fee is Rach’s cut alone and will always look smaller than the real cost. The contract and pool components are charged to your customer but never received by Rach, which is why they are itemised separately rather than folded into one number.
A person can be a customer of several Rach partners at once — one wallet, one address everywhere. A second business gaining the right to act on that wallet requires the customer’s explicit consent. Provisioning a phone that already belongs to another business returns the address with LINK_REQUIRED and no operating rights; run the consent flow:
1

Request to link

POST /v1/users/link/request — Rach sends a one-time code to the customer by WhatsApp/SMS.
2

Customer approves

The customer shares the code with you — possession of the phone is the consent.
3

Confirm the link

POST /v1/users/link/confirm with the code — you become a member (existing members get a user.linked webhook).
After linking, both businesses operate the same wallet independently — each still pays from its own ledger. The original business is never displaced; membership only grows.

Changing a phone number

Because the phone number is the shared lookup key, changing it is consent-gated: a code is sent to the new number (proving SIM control), and the address, balance and history are preserved.
1

Request the change

POST /v1/users/phone-change/request (old + new number).
2

Confirm with the code

POST /v1/users/phone-change/confirm — the lookup key is re-pointed.
Every member receives a user.phone_changed webhook so no one is left pointing at a stale key.

Status lifecycles

Every money-moving call returns 202 Accepted and settles asynchronously. Poll the resource, or subscribe to webhooks and let Rach push the terminal state. See Idempotency & settlement for the full contract. Any of them can end in FAILED, which is terminal and carries an error_reason. A failed funding or transfer returns the value to your ledger — you are never left short. Terminal states are SETTLED, COMPLETED, REFUNDED and FAILED. Treat everything else as “still in progress” and keep waiting. A withdrawal has two legs, and they finish at different times:
Never retry on SUBMISSION_UNKNOWN or PAYOUT_UNKNOWN. They mean Rach could not confirm whether an operation reached the network, or a payout reached the provider, so it may already have happened. Rach reconciles both automatically and moves them to a terminal state with no action from you.Submitting again with a new idempotency key risks paying twice — and a duplicate cash payout to a recipient cannot be reversed. Retrying with the same key is always safe; that is exactly what it is for.
COMPLETED on a withdrawal is stronger than “crypto swept”. It means Rach has recorded durable, attributed evidence that the local-currency payout actually reached the recipient. CRYPTO_RECEIVED means the crypto leg is done but the cash has not landed — so a customer asking “where is my money” is answered by CRYPTO_RECEIVED, not COMPLETED.REFUNDED is the outcome when a payout can never be delivered — a closed account, a dead wallet, a compliance block. The stablecoin goes back to the customer’s wallet, so they hold crypto rather than cash. It is deliberately distinct from FAILED, where nothing ever left the wallet in the first place.

What you can build

The same primitives compose into very different products:

Neobank / wallet app

Provision each customer, pre-fund your ledger, fund wallets on demand. Customers send to each other and to anyone else instantly, in-app.

Cross-border remittance

Pay funds to any recipient by phone number; registered users are notified in-app, unregistered ones cash out via USSD after an identity check.

Mobile-money operator

Bring wallets to feature phones — balance, send and cash-out over USSD / WhatsApp / SMS, gaslessly.

Marketplace / gig payouts

Disburse to workers and sellers by phone number even before they’ve signed up — wallets are created on the fly and the recipient is guided to their money.

Trust model

  • Non-custodial wallets — funds sit in the customer’s own smart-contract wallet on Polygon, with per-transaction limits and timelocked recovery.
  • Phone numbers are never stored in the clear — lookups use a keyed blind index; the address is derived from a permanent identity salt.
  • Consent is cryptographic possession — gaining rights or re-keying a phone always requires a one-time code delivered to that phone.
  • Money-out is member-only and compliance runs before any payout.
  • Signed webhooks — every event is HMAC-signed (X-Rach-Signature) so you can trust its origin.

Sandbox vs live

The key prefix decides the environment: a rach_sk_test_ key runs in sandbox — every B2B call returns an immediate SANDBOX_SIMULATED success without touching the blockchain, moving your ledger, or changing the shared registry. A rach_sk_live_ key executes for real on Polygon mainnet.
Build and test with rach_sk_test_, then switch to rach_sk_live_ — same code, different key.
Sandbox does not execute on a testnet. It short-circuits before the chain entirely and returns a synthetic success, so it validates your request shape, authentication and idempotency handling — but never your handling of real asynchronous settlement.In particular, sandbox always returns SANDBOX_SIMULATED immediately. It will not exercise SUBMITTING → SUBMITTED → SETTLED, will not produce a FAILED, and will never give you a SUBMISSION_UNKNOWN. Build your polling and webhook handling against those states deliberately; sandbox will not force you to.

Keys & webhooks

Generate your CaaS API keys and configure webhook endpoints from your CaaS dashboard (see Get your API keys and Webhooks). Every B2B endpoint is in the CaaS API reference tab.

Embedded goods and services escrow

Use the embedded escrow API to hold USDT on TRON in a dedicated custodial wallet, request release or refund, and raise a dispute for Rach review. This custody model is distinct from the EVM wallet description above. Deposit recovery, signed durable events and tenant-scoped history support reconciliation. Agree the pilot operating terms and confirm environment enablement before collecting funds. Rach P2P trading-interface capabilities are a separate surface.