Programming Bitcoin with Laravel

In a recent project, I constructed a Bitcoin payment solution for an E-Commerce site using Laravel 5.6.

Upon clicking a Pay with Bitcoin button in the E-Commerce site, the user will be transferred to our site for payment.

Bitcoin Payment

The user will be asked to pay to a bitcoin address from his/her desktop or mobile wallet.

When viewed on a mobile device, the user will be shown a Pay button, with a bitcoin:// URL. Clicking the button would open native mobile dialog suggesting to open one of the wallets on the device that registered to the Bitcoin URL scheme. We discovered that desktop wallets, without special setup at the operating system level, do not open when clicking a bitcoin URL.

Payment Collection

At a later time, we would like to pay the merchant from the E-Commerce site the Bitcoin amount pod by the user.

What We Need from Bitcoin

These capabilities:

  1. Watch for payment on a public bitcoin address

  2. Create a raw transaction

  3. Sign a raw transaction with a private key

  4. Broadcast a signed transaction

The Tools

This is a Laravel project, so we need a Laravel package with the power of bitcoin-cli. We used the excellent Bitcoin

Bitcoin JSON-RPC Service Provider for Laravel.

This package provides camel cased functions corresponding to each of Bitcoin Core functions. One can access these functions with the bitcoind helper, as:

bitcoind()

Multisig Addresses for Escrow

We would like to hold the payment in escrow until the goods are delivered to the user. Hence, we request the user to pay to a multi-signature address (multisig):

       $multisig_address = bitcoind()->createMultiSig(2, $public_keys_array);

From which we extract the P2SH and redeem script:

    $p2sh_address = $multisig->result()['address'];

    $redeem_script = $multisig->result()['redeemScript'];

Watching for Payments

A Laravel job would run every minute to check for payments. It used ListUnspent to watch for payment, by adding the target address as a Watch-Only Address using ImportMulti. We would match an unspent transaction on the address based on amount and timestamp. We ask for 0 and more confirmations.

Upon payment, we would update the web view using socket.io using the Laravel Echo Server. We update the web view immediately when 0 or more confirmations are detected.

As is common, we considered a payment to be finalized with 6 confirmations. We save in the database the txid of the payment transaction.

Importing the Address

 $import_result = bitcoind()->importMulti(

    [

        [

            'redeemscript' => $redeem_script,

            'scriptPubKey' => [ 'address' => $p2sh_address ],

            'timestamp' => $time,

            'watchonly' => true,

            'internal' => false,

        ]

    ],

    [

        'rescan' => false,

    ]

);

Get Unspent Transactions

     $unspent_transactions = bitcoind()->listUnspent(0, 999999,  array($address));

Sending the Bitcoin to The Merchant

We split the payment between us the merchant. So we define our $targets as an array mapping addresses to amounts,

array($merchant_address => $amount - $commission, $payment_provider => $commission)

Show Me The Code

    $raw_payment_transaction = $this->get_raw_transaction($payment_txid);

    $vout = $raw_payment_transaction->result()['vout'];

    // check if we need to search over vout

    $our_input = array_filter($vout, function($v, $k) use($input_amount) {

        return $v['value'] == $input_amount;

    }, ARRAY_FILTER_USE_BOTH);

    $used_output = head($our_input);

    $vout_index = $used_output['n'];

    $hex = $used_output['scriptPubKey']['hex'];

    // create raw transaction

    $raw_transaction = $this->create_raw_transaction($payment_txid, $vout_index, $targets);

    $output_details = $this->compose_output_from_transaction($payment_txid, $vout_index, $hex, $redeem_script);

    // sign raw transation

    $composed_transaction_hex = $this->sign_raw_transaction($raw_transaction, $output_details, $private_keys);

Let us break it down.

Retrieve the vout

First, we retrieve the raw payment transaction using the txid using GetRawTransaction.

 $txInfo = bitcoind()->getRawTransaction($txid, 1);

From the raw transaction, we extract the vout to be used. For simplicity the code above just checks for amounts, but it should check for time stamps also. In case there are multiple payments.

Create Raw Transaction

$rawTxInfo = bitcoind()->createRawTransaction(

            array(array('txid' => $input_txid, 'vout' => $input_vout)), $targets

        );

Compose Outputs

    $composed_output = array(array('txid' => $txid, 'vout' => $vout, 'scriptPubKey' => $script_pub_key, 'redeemScript' => $redeem_script));

Sign Raw Transaction

        $signed_transaction = bitcoind()->signRawTransaction($raw_transaction, $output_details, $private_keys);

        $hex = $signed_transaction->result()['hex'];

Send Raw Transaction

If we have all the private keys of the multisig, we could send the transaction,

      $txid = bitcoind()->sendRawTransaction($raw_transaction);

But we really do not most of the time, so we give the raw transaction to the merchant. The merchant signs it his/her private key and broadcasts it to the network. For example using, coinb.in.