ENGINEERING
4th October 2020

Stripe is Easy, Bitcoin is Easier

The third in a series from our engineering team focused on the intersection of money and technology. See our previous from J. Otto on Why Blockchains matter. Check out our open engineering roles here.

Most are familiar with Stripe's "achievement" of simplifying online credit card payments for web merchants to 2 Jira tasks:

  1. add Stripe's credit card widget to your frontend
  2. add an HTTP endpoint to your backend for Stripe to hit (a webhook)

But Bitcoin pioneered a new era of "send money to my public key". An abstraction for the idea of money so simple yet effective enough to do what previously required: Stripe -> credit card companies -> banks -> governments.

Wanna receive Bitcoin as a web merchant? Generate a keypair, hash the public key, email/text the hash (or QR code) to the customer/payer. No KYC, no 3rd party custodian, no governments, and it can be done offline. Instant settlement bliss.

"Come on, all devs and Bitcoin fanatics say that! Talk is cheap! Show me the code!"

It's so easy that the only thinking ("brain damage" as the @TwoBitIdiot would say) you'll have to do is how to manage (rotate, and cold storage) your private keys securely (outside the scope of this article).

We'll use Node.js and the following NPM packages (NPM being the Node.js package manager du jour):

  • electrum-mnemonic (for parsing a seed phrase created from an Electrum Bitcoin wallet)
  • bitcoinjs-lib (for creating bitcoin addresses from a public key)
  • got (for making HTTP requests)
  • satoshi-bitcoin (for handling Satoshi/Bitcoin strings/numbers)

And 2 of our own functions:

createBitcoinAddress() and getBalanceOfAddressInSatoshis()

Technically we could receive all money in a single Bitcoin address, but creating addresses per invoice is a “best practice” because it gives us: pseudo-anonymity, and simpler billing business logic (does the expected balance for an account/invoice exist or not).

So if we're creating Bitcoin addresses (public keys) at scale, here are 2 options:

  1. generate public/private keypair when creating an invoice and persist the keypair to a database
  2. use a single "seed phrase" to generate/fetch infinite public/private keys indexed by integer (HD Wallet)

We'll use the seed phrase approach for 3 reasons: the ability to monitor all addresses created from that seed phrase in the Electrum desktop wallet app, so we only have to store an index, and so we can inject the production seed phrase via environment variable so the keys aren't in a codebase or database.

Here's what it looks like:

const bitcoin = require("bitcoinjs-lib");
// Electrum has their own methodology for seed phrases
// https://electrum.readthedocs.io/en/latest/seedphrase.html
// But fortunately, someone wrote a library to parse them for us
const mn = require("electrum-mnemonic");
function createBitcoinAddress (hdIndex) {
// This is the 12 word seed phrase that you can generate when creating a new wallet in the
// Electrum wallet app (and many other places)
const seedPhrase = process.env.SEED_PHRASE;
const seed = mn.mnemonicToSeedSync(seedPhrase, {
prefix: mn.PREFIXES.segwit,
});
const root = bitcoin.bip32.fromSeed(seed, bitcoin.networks.bitcoin);
// https://ethereum.stackexchange.com/a/70029
// The "derivation path" is where the magic happens (not really), but you need to be
// aware of it so you're generating the same addresses (keypairs) that would exist
// in the Electrum Wallet desktop app (so you can monitor all your addresses from a GUI)
// you see the path format that Electrum uses in its address pane.
// Right click any address and click details to see its derivation path.
const node = root.derivePath(`m/0'/0/${hdIndex}`);
const bech32Address = bitcoin.payments.p2wpkh({
pubkey: node.publicKey,
network: bitcoin.networks.bitcoin,
}).address;
return bech32Address;
};

But what value for the hdIndex parameter should you pass? One simple option is using an auto incrementing serial bigint column from an “invoices” table in a relational database. The database will safely ensure uniqueness of that column so we can use it for creating unique (but retrievable) Bitcoin addresses.

In our frontend, we'd present the address returned by this function call: createBitcoinAddress(invoice.id) and an amount due. Our customer can paste that address into their Bitcoin wallet (we can also present this Bitcoin address as a QR code) and enter/paste the appropriate amount to be paid.

Now for getBalanceOfAddressInSatoshis(), we'll actually need to read from the Bitcoin network. We could run and query our own Bitcoin node directly, or we can hit a public Bitcoin gateway offered by plenty of vendors competing for YOUR business. 2 examples from the first page of google: Sochain or Blockcypher.

const got = require("got");
const sb = require("satoshi-bitcoin");
function getBalanceOfAddressInSatoshis (address) {
return got
.get(`https://sochain.com/api/v2/get_tx_received/BTC/${address}`) // this data can come from a Bitcoin node or any Bitcoin gateway service like sochain, blockcypher
.json()
.then(res => res.data.txs)
// wait for at least 1 confirmation before considering it ours
.then(txs => txs.filter(tx => tx.confirmations > 0))
// parse the stringified bitcoin units into numeric satoshi units
.then(txs => txs.map(tx => sb.toSatoshi(tx.value)))
// sum the results, and yes, JavaScript’s Number.MAX_SAFE_INTEGER > 2100000000000000
// which is the total number of Bitcoin satoshi units that will ever exist)
.then(txSatoshis => txSatoshis.reduce((memo,s) => memo + s, 0))
}


The last part of accepting a payment is the general business logic that would be similar for Stripe and Bitcoin: did I get paid or not?! Here's what that looks like:

const sb = require("satoshi-bitcoin");
const bitcoinAddress = createBitcoinAddress(invoice.id);
const balanceOfAddressInSatoshis = await getBalanceOfAddressInSatoshis(bitcoinAddress);
const balanceOfAddressInBitcoin = sb.toBitcoin(balanceOfAddressInSatoshis);
const paymentDueInBitcoin = 0.01;
if (balanceOfAddressInBitcoin >= paymentDueInBitcoin) {
// 1. update invoices set paid_at = now() where id = $1
// 2. deliver value to customer for completed payment
// 3. possibly send all or some of this to a cold wallet
}

That's it! Bitcoin is readily usable in apps and servers with just a few lines of code. Stripe is easy, Bitcoin is easier.

Come work with us!

If you’re a software engineer interested in helping us contextualize and categorize the world’s crypto data, we’re hiring. Check out our open engineering positions to find out more.