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.
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.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.
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 returningtoken_accounts_ready: true. Do not expose an
address from a failed/503 provision response; retry the same request until it succeeds.
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/statusfor the receive-only / sponsored-send state;GET /v1/dashboard/tron/overview,/wallets,/transfers, and/depositsfor tenant-scoped activity;GET /v1/dashboard/tron/wallets/{blind_index}for a customer’s live USDTbalanceOf.
503 TRON rail is not configured rather than
silently appearing empty.
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.fee_cap_micros: 0 for uncapped.
Flat — one fixed charge per transfer, whatever its size.
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.
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.
Read what you have earned at any time:
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
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
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:Paying to a bank account
Setdestination_type to BANK_ACCOUNT and supply the account instead of a mobile number:
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. Setuse_saved_destination and omit the account details entirely:
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
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, notfee.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 ismax_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.Multi-business wallets (consent)
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 withLINK_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).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.user.phone_changed webhook so no one is left pointing at a stale key.
Status lifecycles
Every money-moving call returns202 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:
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: arach_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.

