Bertha
Balance
$1.00
0.03030 Bitcoins
Send
Receive

A Bitcoin wallet.

Download it, write down your key, and it's yours. Payments land in about a second. No account, no custody, nobody holding your money.

Download Bertha v1.0.1 Bertha, a black and white cow

sha256 · a675622355dc8452d22ad8513965b3f9a1c75632d32ef04181619fc25cba2884

Is your copy genuine?

Do this whenever you like: after downloading, or a month later before you send anything. Pick your bertha.html and the browser compares its fingerprint against every version we have published. Nothing is uploaded; the file never leaves your machine.

How it works

Bertha makes a key on your machine, you write it down, and then you send and receive BSV. Payments land in about a second.

Ultimate Coin (ULT)

Earn ULT by using Ultimate Dot Com. Tap the gold coin in Bertha to see it, send it or receive it.

Bertha
Balance
1,000
1000.00000000 Ultimate Coins (ULT)
Send
Receive

What it is

No account, no email, no sign-up, and nothing of ours ever sees your money or your keys. Bertha reads balances and broadcasts payments through public block explorers: other people's servers, never handed a key.

Read this before you put money in it

The 44 characters you write down are your only real backup. Bertha keeps an encrypted copy in this browser's storage, locked with your PIN, but that copy disappears if you clear site data, move or replace the file, or switch browsers. We can't see it, reset it, or recover it for you. Lose the paper as well and the money is gone permanently.

Save your recovery map too: tap the Map icon at the top left of the wallet. It's a tiny file with no keys in it, recording which addresses you've used. It's the second half of your backup: keep it with your 44 characters so restoring brings your full balance back quickly instead of a slow chain scan. Re-save it now and then as you spend.

Taking payments on your site

Your server watches an address it controls and acts when payment arrives. No SDK, no key exchange, no dependency on us being online. The optional checkout below does run in your visitors' browsers. It is a file you serve yourself and can read in full.

Bertha's Merchant key is the xpub of a branch the wallet watches but never issues from. Your server derives invoice addresses from it, so they can never collide with an address you hand out by tapping Receive, and a compromised server cannot see those addresses at all. It also cannot sign.

awaiting_payment → payment_detected → fulfillment_pending → fulfilled

A fresh address per invoice. Each state written before you act on it, advanced by a conditional update that only succeeds from the previous state. deliver() takes the invoice id as an idempotency key, because after a crash it gets called twice. Unconfirmed is fine for pennies; wait for a block on anything worth stealing.

A working payment server for developers

One Node file, no dependencies, no build step. Upload it to a Node.js 18+ host, connect your site to its API, and replace deliver() with your order-fulfillment code. The invoice machinery and payment detection are done; wiring it to your application is not.

export BERTHA_API_KEY=$(openssl rand -hex 32)
echo $BERTHA_API_KEY                    # your game needs this too
export BERTHA_XPUB=xpub...              # Bertha → Security → Merchant key

BERTHA_FIRST_RUN=1 node bertha-invoices.js      # first start only
node bertha-invoices.js                         # after that

POST /invoice   {"sats": 2000, "reference": "level-3"}
GET  /invoice/:id
GET  /state     → {"merchantXpub": "...", "nextIndex": 126}
POST /state/rebuild                     # if merchant-state.json is lost

It needs a host that runs a persistent Node process. Shared cPanel only works if it offers Setup Node.js App, where you upload the file and set BERTHA_API_KEY and BERTHA_XPUB as environment variables. Dropping it in File Manager does not start anything. Your site's backend calls the endpoints above with that key; the browser never does.

Replace deliver() (it currently just prints to the console) with whatever credits the player or releases the goods, and swap the JSON store for your database. Back up invoices.json and merchant-state.json: an address that was issued but never paid leaves no trace on chain, so nothing can rebuild them. Import the state file into Bertha under Security → Merchant key so a restored wallet knows how far to watch.

It asks block explorers what happened rather than verifying merkle proofs, so large payments are corroborated against a second provider and wait for a block. It refuses to start twice, since one JSON store cannot safely have two writers. Each store is bound to the key that created it. A new merchant branch needs its own database.

Download the invoice server

bertha-invoices.js · sha256 · b3f111ebf55c618cc4db05b5ef42894984c71d648531249140c856d37db3c4c5

And the checkout for your page

The browser half: it renders the QR, polls your backend, and fires one event when the payment lands. Two lines on your page and no other front-end code.

<script src="/bertha-checkout.js" data-api="/pay"></script>

<button data-bertha-item="level-3">Play · 2¢</button>

addEventListener('bertha:paid', e => showPaymentReceived());

The button names the item, never the price. A visitor can edit any attribute on the page, so a price sent from the browser is a price the customer picked. Your /pay route looks up what "level-3" costs in your own catalogue. Both routes are written out in the server file's header, along with where the API key goes, which is never the browser.

The bertha:paid event is cosmetic. Anyone can fire it from the console, so nothing of value may hang off it. Your server's deliver() is what credits the account. The QR carries the address and the amount, so nobody types a figure by hand.

Download the checkout

bertha-checkout.js · sha256 · 7fa17d53ce7c1e4741835e11f3a5bd1c9d384d618d49f5c4800b036a812bebe4