Point-of-sale first · privacy as the wedge

A keyless, private crypto
Point-of-Sale terminal.

A privacy-conscious physical shop takes payment, trusts the numbers, keeps clean books, and pays no processor cut — across Bitcoin, Ethereum/Polygon, Solana, Monero and Tari. The app holds no keys: it only watches the chain for settlement.

◇ Cheaper

No custody → no processor cut, no chargebacks. The customer pays the network fee directly; the merchant keeps the sale. Tari L2 settles at ~0 fee.

⛨ Safer

Privacy coins (Monero, Tari) + observe-only design + a reorg watchdog. The terminal can be stolen and still hold no spendable funds.

7
payment rails, all fail-closed
957
host tests, 0 failing
0
keys held · telemetry SDKs
6
rails settled real money on mainnet

A 90-second walkthrough

Watch a sale happen, tap by tap

The whole job is the cashier loop — run a hundred times a day. Step through it below; the terminal on the right updates as you go. Press play, or click any step.

Auto-play
9:41 · CryptoPoS TESTNET
Sale
$24.00
Oat latte ×2$11.00
Croissant$9.50
Tax (owner add-on 7%)$1.50
Tip 10%$2.00
123456 789·0
Choose a rail · $24.00
BBitcoin
on-chain watcher · live
SSolana
Solana Pay · live
MMonero
view-key · amount hidden
TTari Ootle L2
~0 fee · indexer
Scan to pay · Monero
0.000625 XMR
per-invoice subaddress · 8C9f…u2Rq
Awaiting settlement
seen in mempool ✓
confirmations 7 / 10
crediting exact amount…
app holds no keys — watching only
Settled · receipt printed
Amount$24.00
Token0.000625 XMR
NetworkMonero · stagenet
Confs10 · unlocked
Booked1 ledger row
REAL · TESTNET · CONFIRMED · not income

The full picture

Every capability, in one place

An honest, exhaustive outline — grouped the way an owner thinks about the shop. Tags mark what's real on a test network and what's mechanically enforced by the test suite.

Six payment rails

One router, six rails, one settlement chokepoint

Two detection models. Watch (pull): a keyless on-chain watcher polls the merchant's receiving address. Push / view-key (config-gated): EVM is customer-pays via WalletConnect; Monero, Tari Ootle L2 and Minotari L1 detect from the merchant's own view-key wallet or indexer. A rail with no config falls back to a clearly-marked demo stub and never lights the LIVE badge. Pick a rail:

Honest status: six rails have each settled a real payment on public mainnet (small, operator-funded controlled proofs — see Proofs); the two Tari rails are proven on a self-hosted LocalNet. The app holds no spending key, and mainnet is armed only behind a typed Go-Live checklist — these were proofs of the plumbing, not yet a third-party retail sale.

The product

What the cashier and the owner actually see

Real screenshots from the running app (Galaxy-class emulator, light and dark). The terminal runs dim all day, so the dark “Terminal Slate” theme is a first-class surface.

Cashier terminal, light theme
Terminal · lightKeypad, live total, mode pill + one-line mode explainer
Cashier terminal, dark theme
Terminal · darkTerminal Slate — emerald primary, monospace money
Coin gallery, light
Coins · lightAmount-first per-coin launcher with live/demo status
Coin gallery, dark
Coins · darkBrand-tinted vector coin glyphs
Go-Live checklist
Go-Live checklistEvery rail READY or its exact blocker, before real money
Payment rails settings
Payment railsOne row per rail, tap to configure, live status chip
Settings information architecture
SettingsFour owner sub-screens — Mode & Money / Rails / Store / Security

Architecture

Observe-only by construction

The core invariant: the app never signs, never custodies, never moves funds. It builds a payment request for the merchant's address and watches the chain for settlement. Every rail fails closed on an unreachable or unparseable endpoint, and settles each invoice exactly once.

1 The money never touches the app
  CUSTOMER                      CRYPTO-POS TERMINAL              MERCHANT WALLET
  (their wallet)                (this app — holds NO keys)       (merchant controls keys)
       |                                |                                |
       |  cashier rings up a sale       |                                |
       | <---- scans payment QR -------- |  builds a standards URI        |
       |       BIP-21 / Solana Pay /     |  for the MERCHANT address       |
       |       EIP-681                   |                                |
       |                                |                                |
       | -------- pays on-chain ------------------------------------>     |
       |                                |                                |
       |                                |  WATCHES the chain (read-only) |
       |                                |  keyless watcher / view-key    |
       |                                | <----- settlement confirmed ----|
       |                                |                                |
       |                                |  books the sale + prints receipt
       |                                |  (fail-closed, idempotent)      |
  The app cannot spend, refund, or move funds. It only observes.
2 The payment state machine
  +--------+   generate QR    +-------------------+
  |  IDLE  | ---------------> | AWAITING PAYMENT  |
  +--------+                  +---------+---------+
      ^                                 | tx seen in mempool
      | cancel / new sale               v
      |                        +-------------------+
      |                        | MEMPOOL DETECTED  |
      |                        +---------+---------+
      |                                 | included in a block
      |                                 v
      |                        +-------------------+
      |                        |    CONFIRMING     |  count confs vs a trusted tip
      |                        +----+---------+----+
      |             enough confs |         | unreachable / wrong amount / expiry
      |              +-----------+         +-----------+
      |              v                                 v
      |      +--------------+                  +--------------+
      +----- |  CONFIRMED   |                  |    FAILED    |
   settle    | settle ONCE, |                  | fail closed, |
   then idle | write 1 row  |                  | no ledger row|
             +--------------+                  +--------------+
3 One router, six rails, one settlement chokepoint
                       +------------------------------+
                       |  initiateCheckout (router)   |
                       +--------------+---------------+
       +-----------+----------+-------+-------+----------+-----------+
       v           v          v               v          v           v
  +---------+ +----------+ +---------+   +----------+ +---------+ +-----------+
  | BTC/SOL | |   EVM    | | Monero  |   | Ootle L2 | |Minotari | |  (demo    |
  | keyless | | Wallet-  | | view-key|   | indexer  | | L1 view-| |  stub if  |
  | watcher | | Connect  | | subaddr |   | receipt  | | key gRPC| |  unconfig)|
  +----+----+ +----+-----+ +----+----+   +----+-----+ +----+----+ +-----+-----+
       +----------+---------+--------------+----------+-----------+
                                |
              every rail fails closed; settles ONCE
                                v
        settleActiveInvoice  ->  SettlementMath  ->  Room ledger (+ CSV)
4 Going live is a checklist, not a switch
  tap MAINNET --> GO-LIVE CHECKLIST --> all accepted rails READY? --> type "MAINNET" --> LIVE
                 per-rail status:          | no                        (typed confirm,
                 READY / exact blocker     v                            unchanged hard gate)
                 switch off rails you   Continue DISABLED
                 don't sell yet         (fails closed)
5 The Tari Ootle L2 retail contracts (settlement / loyalty / refund)

Generated from the on-chain template design docs. The app only reconciles these — refunds pay the buyer directly; the terminal never moves funds.

Ootle L2 POS transaction flow diagram Ootle L2 POS architecture diagram

Package map, the full settle-gate rules per rail, and the test layout live in the engineering docs: ARCHITECTURE.md · RAILS.md · INVARIANTS.md.

Field notes · being upfront

What actually happened wiring up Tari Ootle & on-chain minting

Integrating a real wallet, an L2 indexer, and on-chain templates is where the interesting problems live. In the project's honest spirit, here are the moments worth knowing before you build on Ootle — the gotchas, the engine surprises, and what we proved on-chain. Filter by kind:

The full blow-by-blow lives in the repo's dated journals and the Ootle template docs — this is the upfront summary, not a substitute for reading the source.

For blockchain developers

What smart contracts unlock — and can be adopted

The Tari Ootle L2 work isn't just a payment detector; it's a set of on-chain retail templates a merchant or developer can adopt. Each keeps the terminal observe-only — the app reconciles and prepares, the owner signs. Every capability below carries an honest status, never inflated past what's been proven.

Proven on-chain / LocalNet Shipped in-app (host-tested) Designed — not yet proven Deferred — on purpose
Observe-only, always: these contracts can move funds (refunds, withdrawals) — but the app never holds the key that does. It prepares the manifest; the owner signs in their own wallet. That boundary is the whole point, and it's why a few tempting features (in-app loyalty admin, on-the-sale tip-split) are deliberately deferred rather than shipped.

The "cheaper" claim, made legible

Do the math on what you keep

Card acceptance skims a percentage off every in-person sale, then adds chargeback risk on top. A keyless crypto terminal flips that: the customer pays the network fee directly and the merchant keeps the sale. Move the sliders — the blended card rate is grounded in the research below.

Your shop

A rough, honest estimate — not a quote.

Estimated fees you stop paying
$8,400
per year, vs. card acceptance
Cards / yr
$8,400
Keyless crypto / yr
~$0

Card cost = volume × rate × 12 + chargebacks × $25 fee × 12. Merchant-side crypto cost on keyless/L2 rails is approximately zero — the payer covers the network fee, and there are no chargebacks on confirmed on-chain payments. Default rate and chargeback fee are drawn from the cited research.

Charter & safety

The rules every change must defend

Key-less / observe-only

The app holds no spending or admin key, anywhere. The single bounded exception is a store-scoped loyalty key — testnet + loopback only. Any change that makes the app hold a spending key is out of scope, full stop. Grep tripwires enforce it.

Fail closed

Every rail returns “not settled” on an unreachable, unparseable, or ambiguous result — never a fabricated receipt. A write error fails the settle and leaves no row, rather than booking income that didn't arrive.

No telemetry, ever

No analytics, crash-reporting, or measurement SDK of any kind — not disabled, absent. A NoTelemetryTripwireTest scans the dependency catalog, build scripts, source and manifest on every test run, so such a dependency cannot land quietly.

Real money is gated

Mainnet is armed only behind a green Go-Live checklist and a typed “MAINNET” confirmation. The default is always testnet; a fresh or unreadable install comes up safe.

Owner-signs, app-prepares

Administrative actions (e.g. Ootle merchant withdrawals) are prepared in-app and signed by the owner in their own wallet. The owner console is observe-only.

Trustworthy books

Each sale freezes its quote, network and USD value at charge time. The on-screen total, the ledger row, and the exported CSV can never tell different stories — all rendered from one locale-independent money formatter.

Reproducible proofs

Every rail has settled a real payment through the running app

Not unit tests — actual on-chain settlements, booked through the app's normal checkout path. Six rails have each settled real money on public mainnet; the two Tari rails are proven on a self-hosted LocalNet — Tari L2 (Ootle) has no public network yet, and Tari L1 (XTM) has a mainnet but its Tor-only P2P wouldn't sync reliably in this build environment (an honest gap, below). Recipes and full write-ups are in the repo's journals and results docs.

Public mainnet — real money

RailNetworkAmountSettledReference
Solanamainnet$0.25 SOLfinalized, exact-credittx 5B9Myhj…E9vWFX
Solanamainnet$0.25 USDCSPL token, exact-credittx 2iTHS86m…Teev4
Bitcoinmainnet$0.50 BTCconfirmed @ $63,823.75tx 9eeec6c8…d433483f
Ethereummainnet$0.10 / 0.00005974 ETHWalletConnect, confirmedtx 0x1c09b53a…
Polygonmainnet$0.25 USDCWalletConnect, confirmedtx 0x6bf62515…
Moneromainnet$0.10 XMR10+ confs, view-key detecttx 02b1fd21… (#Tx9)
Real income booked: $1.45 across 6 sales. These were small, operator-funded controlled proofs (the agent paid the terminal under a $5 cap) — they prove detection, settlement and bookkeeping end-to-end with real money, not a third-party retail sale. The app held no spending key at any point, and mainnet stays gated behind the typed Go-Live checklist.

Test networks — the Tari rails (no public net) & earlier proofs

RailNetworkAmountSettledReference
Tari L1 (Minotari)LocalNet$5.00 / 9087.27 XTMONE_SIDED_CONFIRMED, view-key gRPCtx 6152428636…
Tari Ootle L2LocalNet$1.20 / 10 XTRCommit&Acceptrcpt_72f1… / 5a9f2c5f…
Bitcointestnet4$1.00 / 1592 satblock 139028tx 1425ca93…43c6
Solanadevnet$0.10 SOLslot 468632915tx hRHeiYw1…eg344
Monerostagenet$0.10 XMR10+ confs, unlockedtx 636f9bd2…f8957bf
EVM (Ethereum)Sepolia$0.10 USDCconfirm + credit checktx 0x4182bb0f…
Honest gaps: the mainnet proofs are controlled self-pays, not yet a real third-party customer sale; Tari L2 (Ootle) has no public network yet; and Tari L1 (XTM) has a public mainnet but it wasn't practical to prove here — Tari mainnet is Tor-only, and a from-scratch sync over Tor stalled at genesis in this build environment, so XTM stands LocalNet-proven (it would complete given a reliable Tari mainnet node to point at). Tracked in ROADMAP.md — the project documents its gaps rather than hiding them.

For developers — AI and human alike

Take it and improve it

Single Gradle :app module, hand-rolled DI, ~940 host tests. Start at AUTOPSY.md — a lean router into docs/autopsy/ with an honest, current breakdown of what's real vs simulated, the invariants, and the roadmap.

# Build the debug APK (AGP needs JDK 21 — the bundled Android Studio JBR)
JAVA_HOME=~/Apps/android-studio/jbr ./gradlew :app:assembleDebug

# Host test suite (Robolectric / JUnit)
./gradlew :app:testDebugUnitTest

# EVM (WalletConnect) needs a Reown project id in local.properties
reown.projectId=YOUR_ID

Where to look first

ui/PosViewModel.kt is the source of truth for the session/cart state and the PaymentSessionState machine; initiateCheckout routes to each rail. The settlement chokepoint is settleActiveInvoice → chain/SettlementMath.kt.

Add or harden a rail

Each rail is a pure parser + a flow under chain/, wc/, monero/, tari/ or minotari/. Parsers are host-tested independent of the network. Honor the settle-gate rules in INVARIANTS.md §1.

The lines you cannot cross

No spending/admin key in the app; fail closed; no telemetry. These are mechanically enforced by grep tripwires and NoTelemetryTripwireTest — a PR that crosses them turns the suite red.

House style for contributions

Conventions (the green-marker push gate, the test-integrity rules, the modular-docs discipline) are in CONVENTIONS.md. New work is ranked against the mission tier-stack — bookkeeping correctness first.

The research

Why "cheaper + safer" holds up

Loading the cited research…

For blockchain developers · ideas → code → usage

Smart-contract templates you can adopt

Each idea below is a real Tari Ootle L2 template, compiled against tari_template_lib rev b1e0308 — most round-tripped on a self-hosted LocalNet, paired with the verbatim source and how to use it (one is a superseded design draft). Each card's status badge states its exact proof level. This is the code-and-usage companion to the capability overview above; the terminal stays observe-only the whole way (it reconciles and prepares, the owner signs).

Proven on-chain / LocalNet Shipped in-app (host-tested) Designed — not yet proven Deferred — on purpose

Build & run recipe — the same four steps for every template

Observe-only, always: these contracts can move funds (settle, refund, withdraw) — but the app never holds the key that does. It prepares the manifest; the owner signs in their own wallet. These Ootle L2 contract proofs run on a self-hosted LocalNet (no public Tari network exists yet); the base-chain rails have settled real money on mainnet (see Proofs). A custody review plus human sign-off remains the named gate before any custodial mainnet use.