Two drop-in widgets and one API. Deposits are credited when the chain says so, payouts are sent exactly once, and every movement is yours to audit.
One per customer account, derived from your own seed. The engine issues it and watches it; you never supply one.
POST /v1/deposit-addressesThe engine reads blocks, waits for depth, and sends a signed webhook. Credited once, even across a restart or a reorganisation.
deposit.confirmedAn idempotency key makes a retry the same payout, never a second payment. It is signed within your limits and followed to finality.
POST /v1/payoutsBoth are iframes on your engine's own origin, so your CSS cannot restyle an address and no third party can serve a different one. Your server opens a session with its credential; the page gets a token that can read that one payment and nothing else.
<div id="cashier"></div> <script src="https://engine.example/embed.js"></script> <script> // Money in. The token came from your server. PaymentsEngine.deposit('#cashier', { token: checkout.token }) .on('status', (state) => { if (state.status === 'CREDITED') closeModal(); }); // Money out. Your server decided it; the customer confirms it. PaymentsEngine.withdraw('#cashier', { token: intent.token }); </script>
The same ticker on two networks is two different instruments. The widget never lets a customer pick one without the other.
A withdrawal is mounted with a token your server got after it checked the account. The widget can confirm it and watch it leave; it cannot change it.
Found, confirming, credited — read from the engine, not inferred from a timer. The parent page is told each change.
| Property | What actually holds it | What it prevents |
|---|---|---|
| A payout pays once | The signed transaction is stored with its nonce before it is sent | A crash, a retry or a second worker can only send the same bytes again; the network accepts them once. |
| A retry never pays twice | UNIQUE (operator, idempotency_key) | A timeout, a duplicated queue message, or a request that crossed a deploy all resolve to one payout. |
| A deposit credits exactly once | UNIQUE (chain, tx_hash, output_index) | A watcher re-reading a block after a restart credits nothing a second time. |
| Limits survive a crash | Sliding 24-hour window under an advisory lock | A limit cannot be reset by killing the process, and is one limit across every instance. |
| Two accounts never share an address | UNIQUE derivation index per key set | A deposit cannot be attributed to whichever account was looked up first. |
| Swept funds go one place | The signer takes no destination for a sweep | Money at a deposit address can only be moved to your own payout wallet. |
Written here rather than discovered in production.
| Capability | State | Consequence for you |
|---|---|---|
| Bitcoin, Tron and other non-EVM networks | Not built | Deposits, sweeps and payouts run on account-model (EVM) networks only. |
| Operator self-onboarding | Interim | Operators are registered and their credentials rotated through an internal admin command, without a deploy. There is no sign-up form an operator fills in themselves. |
| Public network run and third-party audit | Pending | The whole path is exercised against a local chain on every change. It has not been run on a public network, and no external firm has reviewed custody or signing. |
The design assumes it can be. Seeds are sealed under a master key you keep apart from the database, and the signer cannot be asked to sign arbitrary bytes: it builds each transaction itself, checks per-payout and 24-hour limits first, and will only sweep a deposit address to your own payout wallet. That bounds the damage; it does not remove it, and the signer belongs in an HSM before this holds real value.
You need one RPC endpoint per network. Each asset in the catalogue is checked against the endpoint before it is used: the network ID must match and a token must answer with the configured precision, or it is refused.
It says which it is. With no engine behind it the demo is a simulation: signed requests are shown, but no chain is involved. Started with the local-chain command it drives a real engine on a test chain on your machine, and the blocks, transactions and webhooks are real.
The software is not a service and takes no custody: it runs on your infrastructure and signs with your keys. Whether your business needs a licence depends on your jurisdiction and what you do with it.
One command brings up a chain, a test token, the engine and this site, and walks a deposit and a payout end to end on your own machine.