Technical whitepaper

Soma: direct stablecoin settlement.

How a payment gets from a buyer's wallet to a merchant's without anyone holding it in between, and what the system refuses to do.

Version0.1
Launch networkRobinhood Chain
Fee0.8% flat
StatusNot taking real payments

01The problem

Stablecoins settle enormous volume, and almost none of it happens at a checkout. The obstacle is not the money. It is everything a buyer has to understand before the money can move.

A buyer holding dollars cannot spend them until they also hold a second asset to pay for the movement. The same token exists on several networks behind addresses that look interchangeable and are not. Most crypto gateways solve this by taking custody, converting, and paying out on their own schedule, which trades card settlement delays for someone else's balance sheet. And the buyer usually leaves the merchant's storefront for a page carrying another company's name.

Each of those is a place a payment stops. Soma is an attempt to remove them without introducing an intermediary in the process.

02Design goals

  • No custody at any point. Funds move from the buyer's wallet to the merchant's. There is no float account, no payout queue, and no moment where a balance sits in Soma's name.
  • The server never trusts the browser. A payment is confirmed by reading the chain, not by believing what a page reports.
  • One integration across networks. The merchant names a chain; nothing else in their code changes.
  • Exact arithmetic. Amounts are integers in the token's base units from end to end. No floating point touches a balance.
  • Fail closed. A chain with incomplete configuration does not appear. An unreadable price is reported as absent rather than estimated.

03Architecture

Soma is a small server, a hosted checkout page, and an embeddable widget. There is no matching engine and no ledger of customer funds, because it never holds any.

ComponentResponsibility
Payment intentsFee split, expiry, state machine, webhook triggers
Chain verificationReads receipts and balance changes; matches them to invoices
WatcherPolls open intents, confirms payments, retries webhooks
Transaction builderServer-side encoding, balance and allowance checks
CheckoutWallet selection, network switch, signing, confirmation

The checkout runs in an iframe on Soma's origin. The merchant's page never touches the buyer's wallet, and the amount cannot be altered on the client.

Wallet selection

Browser wallets compete for a single window.ethereum global, and the winner is whichever extension injected last. Soma uses EIP-6963 instead: every installed wallet announces itself, the buyer picks, and that provider handles the payment. A buyer who chooses MetaMask is not silently handed to something else.

04Settlement

There are two modes, and they differ in what happens to the fee.

Direct

The buyer transfers the stablecoin straight to the merchant's wallet. No contract is involved, so there is nothing to deploy and nothing to audit before taking a payment. The fee accrues off-chain and is reported on the dashboard.

Contract

The buyer approves the SomaPay contract and calls pay() once. Merchant and fee wallet are paid atomically in that transaction, tagged with a 32-byte order reference. The contract holds no balance between calls, so there is nothing in it to withdraw or lose.

function pay(address token, address merchant, uint256 amount, bytes32 ref) external {
    uint256 fee = (amount * feeBps) / 10_000;
    transferFrom(msg.sender, merchant, amount - fee);
    transferFrom(msg.sender, feeWallet, fee);
    emit Paid(merchant, msg.sender, ref, token, amount - fee, fee);
}

Deployed through CREATE2, the contract occupies the same address on every EVM chain, so an integration stores one string rather than a network-keyed map.

05Matching a payment

In direct mode there is no reference field on the transfer, so an incoming payment has to be attributed some other way. Each open invoice is billed a unique sub-cent amount:

order        120.00 USDC
invoice     120.000617 USDC   <- the last digits are the key

That exact figure is unique among a merchant's open invoices, which makes an otherwise anonymous transfer attributable. It also means a payment can be found when nobody reported it, which happens whenever a buyer pays from an exchange or closes the tab mid-flow: the watcher scans transfers to the merchant's address and matches on value.

This is why amounts are never rounded for display. The sub-cent digits are load-bearing.

06Confirming a payment

A payment reaches succeeded only after the server has independently read the transaction from a node and matched it against the invoice. On EVM chains that means parsing the ERC-20 Transfer log, or the contract's Paid event. On Solana it means reading the token balance change on the merchant's account, which reflects what actually moved regardless of how the transaction was constructed.

requires_payment --buyer signs--> processing --N confirmations--> succeeded
        |                            |
        |                            +-- reverted / wrong amount --> failed
        +-- window elapses --> expired

Four things are refused: an underpayment, a transfer to a different recipient, a transfer of a different token, and a transaction the node has never seen. Nothing the browser claims is taken as evidence.

07Networks

Soma launches on Robinhood Chain. Every other network is live in the same code path.

NetworkFamilyChain IDNotes
RobinhoodEVM4663Launch network. Arbitrum Orbit rollup for tokenised assets.
BaseEVM8453Cheapest path, sub-cent fees.
EthereumEVM1Settlement layer. Buyer covers real gas.
ArbitrumEVM42161High throughput, fast finality.
SolanaSPL-Not EVM. Flat fee per signature.

Solana shares no code with the EVM path beyond the matching rule. It has no chain ID, addresses are base58, there are no Transfer logs, and the wallet signs a serialised transaction rather than a call object. Chains therefore declare a family, and verification, transaction building, and the checkout all route on it.

Payout addresses are validated per family. On Solana that includes rejecting off-curve keys: a program-derived address has no private key behind it, and paying one would send funds somewhere the merchant could never spend from.

A network appears only when its endpoint and token address are both configured. Token addresses are never assumed; the USDC contract on Robinhood Chain was derived from that chain's own gateway router and checked to report USDC with six decimals before it was used.

08Custody and trust

Soma never holds buyer or merchant funds. In direct mode it is not a party to the transaction at all; in contract mode the contract is a pass-through that ends every call with a zero balance.

What Soma does hold is the link between a wallet address and a business: an email, a name, order references, and a payment history. On-chain addresses are public by construction and always will be, so that linkage is the asset worth protecting, and it sits behind session and API-key authentication.

Merchants are notified of payments by signed webhook. The signature exists because a webhook endpoint has to be publicly reachable, which means anyone who finds the URL can post to it. Without verification a forged success message is free merchandise.

soma-signature: t=<unix>,v1=<hmac sha256 of "t.body">

The timestamp is inside the signed material, so a genuine message cannot be replayed later. Merchants who prefer not to verify signatures can treat the webhook as a hint and re-read the payment through the API instead.

09Fees

0.8% of the payment. No monthly fee, no chargeback fee, no cross-border surcharge, and no separate rate for a card that happens to be foreign.

RateOn a $120 order
Soma0.8%$0.96
Card, online2.9% + $0.30$3.78
PayPal3.49% + $0.49$4.68

Network fees are separate and paid by the buyer. They are not Soma's to take, and they are shown live on the home page rather than quoted from memory: gas price comes from each node, and the dollar conversion from Chainlink's on-chain price feed. A chain without a feed reports gas without a dollar figure rather than an invented one.

Fee arithmetic is integer throughout. A $9.42 order splits to 0.07536 and 9.34464, which sum exactly to the original.

10Status and limits

Current state

Soma is not taking real payments. The code is unaudited and access is invite only while that work is finished.

An independent security review is the gate before any real money moves.

What is verified

Fifty automated checks run against live networks. Settlement is exercised without spending anything: the suite takes a real historical USDC transfer, points a merchant account at that transfer's actual recipient, bills just under its value, and submits the real transaction. The invoice settles because the payment genuinely happened on-chain.

  • Payment verification on EVM and Solana, including four ways a wrong transaction is refused
  • Fee arithmetic to the base unit; concurrent invoices each receiving a distinct amount
  • API key and session authentication, and the approval gate
  • Cross-merchant isolation, which returns 404 rather than 403 so it leaks nothing
  • Signed webhook delivery and signature verification

Not yet in scope

  • Contract settlement mode. SomaPay is written but not yet deployed. Direct settlement is the tested path and the one in use.
  • Gasless payment. Scaffolded, pending a bundler and paymaster.

On the way to launch

A durable database, hardened session and payout controls, encrypted secrets at rest, security headers, and an independent audit of SomaPay before contract mode carries real value. The invite-only period exists to work through that list properly rather than in public.

Deployment

Robinhood Chain - launch network, configured and live in the code path. The chain is new, so bridged USDC liquidity is still building; the integration is ready ahead of it.

Base, Ethereum, Arbitrum, Solana - live in the same code path, on the same terms.

SomaPay contract - written, not deployed, not audited.