- PHP 67.2%
- JavaScript 23.7%
- CSS 9.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| admin | ||
| data | ||
| lib | ||
| public | ||
| worker | ||
| .gitignore | ||
| config.example.php | ||
| install.php | ||
| install.sh | ||
| LICENSE | ||
| LICENSE-THIRD-PARTY.md | ||
| README.md | ||
dobbs-cart
A small (BOB) Dobbscoin shop for people who don't want WordPress underneath them. Vanilla PHP, SQLite, no framework. Payments are read from your own Dobbscoin node, prices are set in USD and converted at the live wBOB rate, and the store never needs a private key of its own.
A fork of xmr-cart, the Monero version: same storefront, cart, admin and notifications, with the payment and pricing layers rebuilt for a Bitcoin-style chain.
What it does
- Catalog in batches: product photos with a gallery, share links, per-item
#anchors, and a "Needs a shipping address" switch for digital items. - Cart: several items, one order, one payment. Stock is reserved atomically, so a limited run can't oversell.
- One address per order. The buyer gets a payment page with a
dobbscoin:QR code, a countdown, and a live progress bar that moves as confirmations arrive. - Payments seen at once: a payment shows up while still in the mempool, so a slow or
stalled chain never expires an order that has been paid. It counts as paid at
min_confirmations(6 by default, ~12 minutes). - Refunds tracked: checkout requires the buyer's (BOB) return address (masked, with a show toggle). Over- and underpayments show Refund due in the admin and email you.
- Notifications: new order, paid, refund due, node down / back, price feed down / back, low stock / sold out to you; a payment receipt and a shipped email (with tracking links) to the buyer. Each once per event.
- Privacy: buyer details are erased 30 days after shipping (7 after an order dies), photos are re-encoded (no EXIF/GPS), and the owner emails never carry buyer details.
Requirements
- PHP 8.0+ with
pdo_sqlite,curl,gd,gmp,mbstring - A Dobbscoin full node you run (
dobbscoind) withserver=1and its wallet enabled. The store trusts it to report payments: a node you don't control could report a payment that never happened. - nginx or Apache that can serve PHP, and cron or a systemd timer
Install
git clone https://git.subgenius.finance/SubGeniusFinance/dobbs-cart.git /var/www/dobbs-cart
cd /var/www/dobbs-cart
sudo ./install.sh # or: sudo php install.php
The installer checks PHP, then asks for:
- Store name and public URL.
- Admin email, with [x] send all notifications here (or split new/paid orders, refunds, outages and stock to their own addresses), the From address, and whether buyers get a receipt and a shipping email.
- Your node: RPC URL, user and password. It calls the node and shows the block height, peers and whether the wallet is enabled.
- Where order addresses come from (see below).
- The price feed, which it fetches and shows the current rate from, or a fixed rate.
It generates cookie_secret and setup_key, writes config.php (640), makes data/ and
the upload dirs writable by the web server, and prints the nginx block, the cron lines and
your setup key. Open /admin/, enter the setup key and a passphrase, add a batch and a
product, mark the batch live.
Node settings
dobbscoin.conf on the node needs at least:
server=1
rpcuser=…
rpcpassword=… # long and random
rpcbind=127.0.0.1 # keep RPC off the internet
rpcallowip=127.0.0.1
Leave the wallet enabled (no disablewallet=1). Keep the node and the store on the same
box, or reach the node over a private network, never over the public internet.
Payment addresses: two modes
Chosen at install (address_mode in config.php):
| rpc (default) | pool | |
|---|---|---|
| Where addresses come from | The node's wallet makes a fresh one per order (getnewaddress, labelled dobbs-cart #N) |
Addresses you generate in your own wallet and paste into the admin's Addresses tab |
| Keys on the server | Yes: the node wallet holds them. Back it up and sweep it like any hot wallet | None. Each address is imported watch-only; payments land in your wallet |
| Setup | Nothing | Keep the pool topped up; the admin warns below pool_low_warning (20) and checkout stops at 0 |
Either way the store reads payments from the node's wallet view of that address
(listtransactions, watch-only included), so it sees a payment in the mempool and counts
confirmations per block. A pool address must be fresh and used nowhere else: anything sent
to it counts toward its order.
⚠ (BOB) addresses use the same format as Bitcoin addresses (they start with 1 or 3), so a BTC address passes as a valid (BOB) return address. The store can't tell them apart.
Pricing: the wBOB rate
Prices are set in USD. At checkout the total is converted to (BOB) at the live wBOB
price on Gnosis and locked for the quote window (order_ttl_minutes, 60).
The default feed, https://subgenius.finance/pools.json, lists the wBOB pools; the store
uses the deepest one (by TVL) that isn't stale. Any feed that returns
{"price_usd": 0.0123} works too (price_feed_url).
wBOB liquidity is thin, so one trade can move the price. The store protects itself:
- a feed older than
price_feed_max_age(1 h) counts as no price; - a new price under half or over double the last good one (under 6 h old) is rejected;
- with no trustworthy price, checkout pauses (and emails you) rather than mispricing.
Prefer to set the rate yourself? manual_coin_rate (USD per 1 BOB) overrides the feed.
Running the workers
* * * * * www-data php /var/www/dobbs-cart/worker/poll.php >> /var/log/dobbs-cart.log 2>&1
17 3 * * * www-data php /var/www/dobbs-cart/worker/purge_pii.php >> /var/log/dobbs-cart-pii.log 2>&1
poll.php checks every open order each minute and sends the node / price health alerts.
Both exit quietly until config.php exists. No cron? Use a systemd timer that runs the
same commands.
Demo mode
With no node_rpc_url, the store runs on fake addresses and a fake chain so you can try
the whole flow: place an order, then use Simulate payment in the admin.
Resetting the admin passphrase
sqlite3 data/store.sqlite "DELETE FROM kv WHERE k='admin_pass_hash';"
The next /admin/ visit brings the first-run page back (it asks for setup_key).
Security posture
- The admin is passphrase-gated (bcrypt) and rate-limit friendly; a passphrase change ends
every session. The store refuses to run with a weak
cookie_secret, and first-run setup needs thesetup_keyfromconfig.php. - In pool mode the server holds no keys at all; in rpc mode the node wallet does.
- Buyer details are erased on a schedule; owner emails never include them.
config.php(it holds the node RPC password) is git-ignored and written 640.
Hardening the admin
For a shop taking real orders, add a second layer at the web server so the passphrase form is never offered to the public internet:
- Bind admin/ to a VPN or Tailscale interface (a separate
serverblock that listens only on that IP), or - localhost + SSH port-forward (
listen 127.0.0.1:8089;, thenssh -L 8089:127.0.0.1:8089 host), or - HTTP Basic auth in front of
/admin/.
If admin/ stays public, at least rate-limit /admin/login.php (nginx limit_req, a few per
minute).
Credit
Fork of xmr-cart (MIT). The QR code generator is Kazuhiko Arase's (MIT); see LICENSE-THIRD-PARTY.md.
License
MIT. See LICENSE.