Creating Bitcoin custom contracts with Elixir


11 months ago by libs

I’ve been busy with some new projects recently. Part of that has involved re-writing my Elixir Bitcoin library, BSV-ex. I’ll be releasing BSV-ex 2 soOoOn - it’s been entirely rewritten from the ground up with many improvements.

One thing I’ve spent time on, is thinking about how to define contracts and build transactions in a developer friendly way. I think I’ve come up with something pretty unique and I’m excited to share it.

Defining contracts

BSV-ex 2 ships with a Contract behaviour module. To implement the behaviour, all we have to do is define a module with locking_script/2 and unlocking_script/2 functions. I’ll use a P2PKH contract as a simple example.

defmodule P2PKH do
  use BSV.Contract
  def locking_script(ctx, %{address: address}) do
    |> op_dup
    |> op_hash160
    |> push(address.pubkey_hash)
    |> op_equalverify
    |> op_checksig

  def unlocking_script(ctx, %{keypair: keypair}) do
    |> sig(keypair.privkey)
    |> push(BSV.PubKey.to_binary(keypair.pubkey))

That’s pretty easy to follow along and reason about, right? This is just pure Elixir so we can easily add our own helper functions that act as macros for defining more complex but commonly repeated script templates. For example, it would be pretty trivial to define an op_push_tx() function we could call in our unlocking scripts.

BSV-ex 2 also comes with a full-featured Bitcoin Script VM, and the Contract module allows us to test and simulate our contracts.

keypair =
lock_params = %{address: BSV.Address.from_pubkey(keypair.pubkey)}
unlock_params = %{keypair: keypair}

with {:ok, vm} <- BSV.Contract.simulate(P2PKH, lock_params, unlock_params) do
# -> true

Building transactions

Anyone familiar with my JavaScript library TxForge will recognise the influence here. This is how we build transactions in BSV-ex 2.

utxo = BSV.UTXO.from_params(utxo_params)

builder = %BSV.TxBuilder{
  inputs: [
    P2PKH.unlock(utxo, %{keypair: keypair})
  outputs: [
    P2PKH.lock(10000, %{address: address}),
    OpReturn.lock(0, %{data: ["hello", "world"]})

tx = BSV.TxBuilder.to_tx(builder)
rawtx = BSV.Tx.to_binary(tx, encoding: :hex)

I hope you like the way this is heading. Elixir remains somewhat under-recognised as a language for Bitcoin development, but I can assure you the tools are there to consider it a top-tier language for Bitcoin and Bitcoin-app development. I hope I can convince a few more of you to try it out.

BSV-ex 2 will be released in the next 7-10 days.