Layer-1

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
// Use this library to parse them for us
const mn = require("electrum-mnemonic");

function createBitcoinAddress(hdIndex) {
// The 12-word seed phrase generated when creating
// a new Electrum wallet (and most other wallets)
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
// use the same "derivation path" that Electrum uses
// so the seed phrase maps to the same addresses
// that Electrum would show
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) {
// 1. this data can come from a Bitcoin node or
// any Bitcoin gateway service like sochain,
// blockcypher
// 2. wait for at least 1 (usually this should be 6+)
// confirmation(s) before considering it ours
// 3. parse the stringified bitcoin units into
// numeric satoshi units
// 4. 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)
return got
.get(`https://sochain.com/api/v2/get_tx_received/BTC/${address}`)
.json()
.then((res) => res.data.txs)
.then((txs) => txs.filter((tx) => tx.confirmations > 0))
.then((txs) => txs.map((tx) => sb.toSatoshi(tx.value)))
.then((txSatoshis) => txSatoshis.reduce((memo, s) => memo + s, 0));
}

The final part of accepting a payment is the general business logic that would be similar for Stripe or Bitcoin: did I get paid or not?! Here's what that could look 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.

-J. Otto

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 roles for frontend and backend engineers and drop us a line at careers@messari.io.

Let us know what you loved about the report, what may be missing, or share any other feedback by filling out this short form. All responses are subject to our Privacy Policy and Terms of Service.
Mentioned Assets

Suggested Research Based on your Watchlists

Create a new watchlist
Mentioned Assets