Cardano meets Elixir and Phoenix LiveView
For Part 2 follow this Link
(Part 1) Setting up Phoenix with Typescript
I want to take you along a mini series of my experiences trying to merge some of my favorite technologies: the Cardano blockchain and the Elixir programming language with the Phoenix LiveView web framework.
This series assumes basic knowledge about the workings of Phoenix Liveview and Cardano’s eUTXO. For more background on these topics I would like to recommend the Phoenix Liveview course of Pragmatic Studio and the Plutus Pioneer Program
The first part will describe the setup of typescript together with a Phoenix LiveView application. We are going to need this because the libraries we will be using are JavaScript/wasm libraries. And since we are going to need to venture into JS world why not use TypeScript while we are at it.
The database we will setup with docker compose, this is a quick and easy way to spin up a PostgreSQL DB together with an pgAdmin instance.
The second part will go into the meat and potatoes of this series, sending t₳ on the preview network from your nami testwallet to another address on the preview chain. We will use the cardano-serialization-lib and the @cardano-sdk/dapp-connector. These are both fairly low level libraries, maybe a bit harder to get started but great if your objective is to learn. Some great information about the cardano-serialization-lib can be found here.
There are many amazing tools within the Cardano ecosystem such as Mesh.js or Lucid. What these tools have in common though is that they require a NodeJS environment. For this project that has been a blessing and a curse, because we cannot build on top of these amazing libraries, but a blessing because it requires us to dive a little deeper and learn a bit more.
Setting up the Database
This is the standard docker compose file I use for all my elixir projects. It spins up a PostgreSql instance together with a PGadmin. If you like me to elaborate more on this setup let me know in the comments, otherwise run: docker compose up
services:
postgres:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
volumes:
- postgres:/var/lib/postgresql/data
networks:
- postgres
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
interval: 10s
timeout: 5s
retries: 5
pgadmin:
image: dpage/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL: admin@pgadmin.com
PGADMIN_DEFAULT_PASSWORD: postgres
PGADMIN_LISTEN_PORT: 15432
ports:
- 15432:15432
volumes:
- pgadmin:/var/lib/pgadmin
depends_on:
- postgres
networks:
- postgres
volumes:
postgres:
pgadmin:
networks:
postgres: {}
Phoenix LiveView
Create a new phoenix liveview project mix phx.new clive
(short for Cardano Live).
Go to the directory cd clive
.
We will create a simple page to store addresses and users.
mix phx.gen.live Transaction SendTest send_tests address:string amount:integer
Do not forget to add all the routes to the Router.ex file. Otherwise the page will not be accessible.
Alice and Bob
We need to set up some addresses to receive the funds of our test. First we need to setup some keys. This can be done with the cardano-cli for Alice and bob: cardano-cli conway address key-gen — verification-key-file alice.vkey — signing-key-file alice.skey
After the keys are generated you can create an address from the vkey:
cardano-cli address build — payment-verification-key-file alice.vkey — testnet-magic 2
Now add their address and the amount of t₳ (in loveless) you want to send to this address in our newly created web app. The generators should have created a form for adding send tests similar to the one below.
TypeScript and WASM
ESBuild compiles typescript out of the box, but we want to have a little more control over our compiler so we will use the esbuild-plugin-tsc.
The cardano-serialization lib uses WASM, so we need the esbuild-plugin-wasm
to be able to include this in our webapp. The Phoenix documentation has an excellent section on how to setup esbuild plugins. In the interest of brevity I will not repeat what has been excellently explained there but in order to follow along it is vital that you:
- Go to the assets directory
- run
npm install esbuild typescript --save-dev
- make sure the type in package.json is set to module
run npm install ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view --save
- add the build.js to your project
- remove the esbuild watcher from dev.exs add the following to the watchers:
node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)],
- in mix.exs remove the esbuild commands and change the assets.build alias to:
“assets.build”: [“npm run tsc”, “tailwind clive”, “cmd — cd assets node build.js — deploy”],
npm install esbuild-plugin-tsc esbuild-plugin-wasm --save-dev
- add the plugins to the build.js
import esbuildPluginTsc from "esbuild-plugin-tsc";
import { wasmLoader } from "esbuild-plugin-wasm"
...
const plugins = [
wasmLoader(),
esbuildPluginTsc({
force: true
}),
];
- remove esbuild from mix.exs and its config from config.exs
Some additional adjustments need to be made. The libraries we would like to use require JS modules to be enabled so we need to adjust our configuration.
lets start with our tsconfig.json configuration:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"ts",
"custom.d.ts"
]
}
Make sure to add a ts folder and an empty custom.d.ts file.
in our build.js we need to update the options:
let opts = {
entryPoints: ["js/app.js"],
bundle: true,
logLevel: "info",
target: "esnext",
outdir: "../priv/static/assets",
external: ["*.css", "fonts/*", "images/*"],
nodePaths: ["../deps"],
loader: loader,
plugins: plugins,
format: "esm"
};
An important step we should not forget is to update the root.html.heex
template so the js in the liveview knows the app uses modules:
<script defer phx-track-static type=”module” src={~p”/assets/app.js”}>
These changes also require use to change the topbar.js so it will mix with our new settings. This is easily done however by changing the extension of the file to topbar.cjs
, make sure to update the import in app.js
Hooking into LiveView
Before writing our typescript app in the next episode we will test our setup with a simple hello world from typescript. assets/ts/main.ts
:
export function hello(): string {
return "hello from ts"
}
Bridging our typescript app into Phoenix LiveView can be done with Hooks. Lets set them up in our app.js:
import { hello } from "../ts/main"
let Hooks = {}
...
let liveSocket = new LiveSocket("/live", Socket, {
hooks: Hooks,
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken }
})
Hooks.Hello = {
mounted() {
this.el.addEventListener("click", e => {
const h = hello();
console.log("received event")
console.log(h);
this.pushEvent("hello", h)
})
}
}
The logs are not strictly necessary but when we are learning is good to see what is going on.
Now in our send_test_live we would like to receive the hello message, so we need to add a handle_event function in the index.exs
def handle_event("hello", msg, socket) do
socket = assign(socket, hello: msg)
{:noreply, socket}
end
The final step will be adding a button to our LiveView to trigger hello and a div to display the text we received:
<div>
<.button id="hello-btn" phx-hook="Hello">
Hello
</.button>
<div><%= @hello %></div>
</div>
After pressing the button we should see logs in the browser console:
the iex session :
and a very tacky hello from ts on our web page:
This is part 1 of our integration adventure. In part 2 we will delve deeper into creating a transaction and signing it with our Nami wallet.
For Part 2 follow this Link
Disclaimer
The code shown in this series is intended for educational purposes only and is not full production ready code. You are free to reuse any content provided by this blog but a link back to the source would be greatly appreciated!
I do not assume liability for any losses of funds that result from mistakes either made by me in this blog or made by the reader. Following along is completely at your own risk!! I implore you to inform yourself well about the differences between main and test nets and to check transactions meticulously before you sign them.