Lucky Tutorial - Let's build a Bitcoin wallet

Installing Crystal and the Lucky Framework.

First you have to add the repository to your APT configuration. For easy setup just run in your command line:

curl -sSL https://dist.crystal-lang.org/apt/setup.sh | sudo bash

Once the repository is configured you're ready to install Crystal:

sudo apt install crystal

Call crystal -v to check everything installed correctly.

$ crystal -v Crystal 0.25.1 [b782738ff] (2018-06-27) LLVM: 4.0.0 Default target: x86_64-unknown-linux-gnu

Install Lucky

git clone https://github.com/luckyframework/lucky_cli cd lucky_cli git checkout v0.11.0 shards install crystal build src/lucky.cr --release --no-debug sudo cp lucky /usr/local/bin

Call lucky -v and you should get the version number you specified in the git checkout above.

Create a project

First, you'll need to make sure you postgres installed and running. For most Linux install the following is enough.

$ sudo service postgresql start * Starting PostgreSQL 9.3 database server

Now go ahead and create a lucky project.

$ lucky init bitcoin_wallet Generating crystal project for bitcoin_wallet Adding Lucky dependencies to shards.yml Done generating your Lucky project ▸ cd into bitcoin_wallet ▸ run bin/setup ▸ run lucky dev to start the server

Lucky tries to be as friendly as possible so as you can see if gives you the next steps. Let's follow those

$ cd bitcoin_wallet $ bin/setup Yarn is not installed. See https://yarnpkg.com/lang/en/docs/install/ for install instructions.

Lucky does come with a list of requirements before installation. I've kind of skipped that so far, so if you don't have Yarn imnstalled do the following.

$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - OK $ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list deb https://dl.yarnpkg.com/debian/ stable main

Then you can simply:

sudo apt-get update && sudo apt-get install yarn

Running bin/setup one more time should give you a long list of package install then eventuall a command prompt hopefully with no errors.

$ bin/setup

It's also worth setting a password in postgres for your user id and adding that to the database config.

$ psql psql (9.3.18) Type "help" for help. ubuntu=# \password Enter new password: Enter it again: ubuntu=# \q

Your config/database.cr should now have the following section

Avram::Repo.configure do if Lucky::Env.production? settings.url = ENV.fetch("DATABASE_URL") else settings.url = ENV["DATABASE_URL"]? || Avram::PostgresURL.build( hostname: "localhost", database: database, username: "ubuntu", password: "password") end # In development and test, raise an error # if you forget to preload associations settings.lazy_load_enabled = Lucky::Env.production? end

Integrated web and build server catches compile errors

Run the Lucky server in development and it will look for changes to any of your source files. Changes trigger asset compilation for JavaScript and Sass. For changes to Crystal source files the compiler will run and you will see any errors in the console.

$ lucky dev

The lucky combined web and build server.

Database Migrations

Database migration allow us to easily add and modify tables in our application. We first generate a migration file and then add our columns to the crystal source code.

$ lucky gen.migration CreateKeyPair Created CreateKeyPair::V20180810125258 in ./db/migrations/20180810125258_create_key_pair.cr

Edit the newly generated migration file and add the following code.

class CreateKeyPair::V20180810125258 < LuckyMigrator::Migration::V1 def migrate create :key_pairs do add user_id : Int32 add public_key : String add private_key : String end end def rollback drop :key_pairs end end

The we can migrate the database to add our new columns.

$ lucky db.migrate Migrated CreateKeyPair::V20180810125258

Type Safe Pages and Layouts (And how to style with SCSS)

Out of the box lucky already comes with authentification code and a user model. It also generates a bunch of pages to get you started.

Lucky doesn't use templating to generate pages. Lucky pages are pure crystal code. That means they are checked by the compiler and the rendering code is crystal. In my opinion this is a huge time saver and benefit over other frameworks. Here are the pages lucky generates for you.

$ tree src/pages/ src/pages/ ├── errors │ └── show_page.cr ├── guest_layout.cr ├── main_layout.cr ├── me │ └── show_page.cr ├── password_reset_requests │ └── new_page.cr ├── password_resets │ └── new_page.cr ├── sign_ins │ └── new_page.cr └── sign_ups └── new_page.cr

Example Code for Sign Up form rendering

Below is a code sample for one of the generated pages. HTML elements such as H1 are method calls which take parameters or blocks of more elements.

Parts of the page can be split into functions to be re-used or to make the code easier to maintain.

class SignIns::NewPage < GuestLayout needs form : SignInForm def content h1 "Sign In" render_sign_in_form(@form) end private def render_sign_in_form(f) form_for SignIns::Create do sign_in_fields(f) submit "Sign In", flow_id: "sign-in-button" end link "Reset password", to: PasswordResetRequests::New text " | " link "Sign up", to: SignUps::New end private def sign_in_fields(f) field(f.email) { |i| email_input i, autofocus: "true" } field(f.password) { |i| password_input i } end end

Things to note..

Links are using type safe destinations. i.e. PasswordResetRequests::New

To use a layout we just inherit from it. < GuestLayout

No need to learn a templating language we are using pure crystal code.

The default unstyled sign up process

Here's how the signup process currently looks.

Out of the box lucky sign up and sign in.

The authentication pages are unstyled so this is a great opportunity to dive in and look at how lucky handles layouts, pages and CSS in the asset pipeline.

Altering the provide GuestLayout

It would be tempting at this point to bring in a CSS framework like bootstrap and alter the generated pages by adding classes and so on.

However, as we're learning a new framwork now is also a good time to revisit CSS and see how much styling we can do without adding too much markup into our crystal pages. All of the authentication pages inherit from GuestLayout so let's add a little bit of markup there.

abstract class GuestLayout # Edit shared layout code in src/components/shared/layout.cr include Shared::Layout def render html_doctype html lang: "en" do shared_layout_head # Add a class to the body element. body class: "authentication" do # surround the content with a main element. main do render_flash content end end end end end

Using the built in CSS processing.

Lucky uses SCSS as a CSS extension framework. This gives us two main benefits. Firstly we can structure our css into seperate files and lucky already provides the folders for this. Secondly, we can leverage some CSS extension provided by SCSS tsuch as nested CSS and mixins.

$ tree src/css/ src/css// ├── app.scss/ ├── components/ ├── mixins/ └── variables/

So let's create 2 new files. Firtly color.scss in the variables folder. Here we can define colours and use them throughout our style sheets.

$color-white: white; $color-grey: #fafafa; $color-dark-grey: #333;

Secondly, we'll create components/authentication.scss which is the main styling to apply to our authentication pages.

We're making use here of CSS Flexbox which in my opinion is well worth learning if you don't know it already.

.authentication { background-color: $color-grey; h1 { text-align: center; text-transform: uppercase; color: $color-dark-grey; } form { display: flex; flex-direction: column; } form > * { margin-bottom: 1em; } main { background-color: $color-white; width: 500px; margin: 3em auto 0 auto; padding: 2em; border-radius: 8px; flex-wrap: wrap; flex-direction: column; justify-content: center; box-shadow: 0 10px 40px -14px rgba(0, 0, 0, 0.25); } input { font-size: 16px; padding: 15px; border-radius: 2px; border-width: 1px; border-style: solid; border-color: rgba(40, 47, 55, 0.1); } input[type="submit"] { font-size: 12px; font-weight: 600; text-align: center; text-transform: uppercase; letter-spacing: 1px; cursor: pointer; background-color: rgb(14, 125, 255); color: rgb(255, 255, 255); padding: 14px 20px; border-width: 0px; border-radius: 1px; } } @media (max-width: 600px) { .authentication main { width: 95%; margin-top: 1em; } }

Finally we import our new files into app.css

// Lucky generates 3 folders to help you organize your CSS: @import "~normalize-scss/sass/normalize/import-now"; @import "variables/colors"; @import "components/authentication"; body { font-family: Sans-Serif; }

After changing the layout adding our CSS and updating our me page you should be able to register and be presented with something that looks like the screenshot below.

The final result

The newly styled sign up and sign in forms.

Generating Actions and Pages

Rather than having potentially bloated controllers handling many incoming requests Lucky handles each request in a seperate class. Let's create an action.

class Bitcoins::Index < BrowserAction get "/bitcoins" do addresses = Array(Tuple(String, String, String)).new render IndexPage, addresses: addresses end end

The get "bitcoins" method tells lucky to create a get route that goes to this action. We'll need to add a page that correspons to the IndexPage we've asked the action to render.

class Bitcoins::IndexPage < MainLayout needs addresses : Array(Tuple(String, String, String)) def content h1 "Your Bitcoin Addresses" table class: "table" do thead do tr do th "Label" th "Public Address" th "Balance" end end tbody do @addresses.each do |label, public_key, balance| tr do td label td public_key td balance end end end end end end

Our new page extends MainLayout which is provided by lucky. This layout requires the user to be logged in.

Redirecting login to our new page

So now we need to direct users to our bitcoins page when they log in. Edit your src/actions/sign_ins/create.cr and chnage the redirect so it looks like the one below.

Authentic.redirect_to_originally_requested_path(self, fallback: Bitcoins::Index)

It's now time to log into your new Lucky application. You'll see something like the following.

The newly styled sign up and sign in forms.

You can run lucky routes to see what routes are created for you and the ones you added.

$ lucky routes ╔════════╦════════════════════════════════╦═══════════════════════════════╗ ║ Verb | URI | Action ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | /bitcoins | Bitcoins::Index ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | / | Home::Index ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | /me | Me::Show ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ POST | /password_reset_requests | PasswordResetRequests::Create ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | /password_reset_requests/new | PasswordResetRequests::New ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ POST | /password_resets/:user_id | PasswordResets::Create ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | /password_resets/:user_id/edit | PasswordResets::Edit ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | /password_resets/:user_id | PasswordResets::New ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ POST | /sign_ins | SignIns::Create ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ DELETE | /sign_out | SignIns::Delete ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | /sign_in | SignIns::New ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ POST | /sign_ups | SignUps::Create ║ ╠────────┼────────────────────────────────┼───────────────────────────────╣ ║ GET | /sign_up | SignUps::New ║ ╚════════╩════════════════════════════════╩═══════════════════════════════╝

Installing Javascript dependencies with Yarn

We'll create our Bitcoin private keys in the browser and encrypt them before they are sent to the server and stored in the database. This adds a level of security to our wallet.

To do this we'll use BitcoinJS to handle browser side key creation. We install BitcoinJS with Yarn a tool for managing javascript package dependencies. This is the recommended way of using javascript libraries in your application.

$ yarn add bitcoinjs-lib --ignore-engines

And you should get the following result.

To test this add the following code to your src/js/app.js. Then refresh the page and you should see a Bitocin Testnet address generated in your console.

import Bitcoin from "bitcoinjs-lib" const testnet = Bitcoin.networks.testnet const keyPair = Bitcoin.ECPair.makeRandom({ network: testnet }) console.log(Bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: testnet }).address)

Lucky data persistance and validation

We already migrated our database and created a keypair table. So let's connect to this table and start to create key pair records.

In lucky we need to create a model.

require "./user" class KeyPair < BaseModel table :key_pairs do column label : String column public_key : String column private_key : String belongs_to user : User end end

When you define a model, Lucky creates a corresponding form class. This is a way to encapsulate validation logic. We need to create our form and tell lucky which fields we allow to be defined.

class KeyPairForm < KeyPair::BaseForm fillable label fillable public_key fillable private_key end

Updating the page and actions for our new form

We'll add a method to generate a HTML form and call it form our page.

class Bitcoins::IndexPage < MainLayout needs addresses : Array(Tuple(String, String, String)) needs form : KeyPairForm def content render_key_pair_form(@form) h1 "Your Bitcoin Addresses" table class: "table" do thead do tr do th "Label" th "Public Address" th "Balance" end end tbody do @addresses.each do |label, public_key, balance| tr do td label td public_key td balance end end end end end private def render_key_pair_form(f) form_for Bitcoins::Create do label_for f.label text_input f.label hidden_input f.public_key hidden_input f.private_key submit "Create Bitcoin Address", id: "create-address" end end end

We need to create a new action called Create that will store the KeyPair into the database for us.

class Bitcoins::Create < BrowserAction route do KeyPairForm.create(params, user_id: current_user.id) do |form, key_pair| if key_pair flash.info = "Bitcoin address successfully generated" redirect to: Bitcoins::Index else flash.failure = "Unable to generate Bitcoin Address, Please try again." addresses = KeyPairQuery.new.user_id(current_user.id).map{ |keypair| { keypair.label, keypair.public_key, "" } } render IndexPage, form: form, addresses: addresses end end end end

And we need to update our IndexAction as the compiler will complain unless we start passing a form into our page.

class Bitcoins::Index < BrowserAction get "/bitcoins" do addresses = Array(Tuple(String, String, String)).new render IndexPage, addresses: addresses, form: KeyPairForm.new end end

Creating keys in the browser

We want to add a handler to the button that creates the public and private keys and populates the hidden fields of the form. We can do that quite simply by adding the following code into your src/js/app.js

function $(id) { return document.getElementById(id) } import Bitcoin from "bitcoinjs-lib" document.addEventListener("DOMContentLoaded", ()=>{ const btn = $('create-address'); if(! btn) return btn.onclick = ()=>{ const testnet = Bitcoin.networks.testnet const keyPair = Bitcoin.ECPair.makeRandom({ network: testnet }) $('key_pair_public_key').value = Bitcoin.payments.p2pkh( { pubkey: keyPair.publicKey, network: testnet }).address $('key_pair_private_key').value = keyPair.toWIF() return true } });

If you're used to using JS libraries like JQuery, now's a good time to rethink that. Here we can write ES6 style JavaScript and Lucky will transpile this into something most browsers understand via Babel.

Additional styling

The design is looking a bit sad at the moment so let's add some SCSS to make it look a bit nicer. You can add the SCSS from the codepen below into your CSS folder.

Querying the database and showing results

When you define a model, Lucky creates a query class that you can use to access the database. In our case it's called called KeyPair::BaseQuery. To use it, let's create a query object that inherits from the one generated by Lucky.

class Bitcoins::Index < BrowserAction get "/bitcoins" do addresses = KeyPairQuery.new.user_id(current_user.id).map { |key_pair| { key_pair.label, key_pair.public_key, "0" } } render IndexPage, addresses: addresses end end

We can add our own method to the query but Lucky generates lots of methods for us. And in most cases this would be enough to get the data you need.

The final result

Succesfully adding data to the database.

Installing dependencies with Shards and retrieving balances

Dependency management is very easy in Crystal using shards. This is similar to Bundler that comes with rails with the added security benefit of also using a lock file. Add the following to your shards.yml

github: onchain/onchain-shard

And then run shards to install your new dependency.

$ shards install ... Fetching https://github.com/onchain/onchain-shard.git ...

You'll need to register with https://onchain.io and set the API key into your environment. When you've done that we can call out to onchain and view the results.

class Bitcoins::Index < BrowserAction get "/bitcoins" do addresses = populate_balances(KeyPairQuery.new.user_id(current_user.id)) render IndexPage, addresses: addresses, form: KeyPairForm.new end private def populate_balances(key_pair_query) just_addresses = key_pair_query.map{ |keypair| keypair.public_key } balances = OnChain::API::Address.get_balances( "testnet3", just_addresses.join(",")) case balances when OnChain::API::Balances return key_pair_query.map{ |keypair| bal = balances.addresses.find { |a| a.address == keypair.public_key } human_bal = "0" human_bal = bal.human_balance.to_s if bal { keypair.label, keypair.public_key, human_bal } } else puts balances return key_pair_query.map{ |keypair| { keypair.label, keypair.public_key, "" } } end end end

Conclusion

There's more we can do with the wallet like giving the user the ability to spend. However I think we've covered enough to give a good overview of how Lucky works.

With the lucky framework we have all the power of Rails but with the added benefit of the compiler. The code footprint is small and the quality to me feels higher.