How to Build Your Own dApp with Phoenix LiveView and VeChain
Disclaimer: This article makes some assumptions about your familiarity with Elixir, Phoenix, JavaScript, and VeChain. If you're not familiar with these technologies, I recommend you check out the Phoenix and VeChain developer documentation.
TL;DR: This post is a rundown of how I built the website you're reading this on now. I used Phoenix LiveView to build a dApp that interacts with the VeChain blockchain. I'll be going over the steps I took and how you can build your own.
Setting Up The Project
First, we need to create a new Phoenix project. We can do this by running the following command:
mix phx.new hello_veworld --no-ecto --module HelloVeWorld
This will generate a new Phoenix project with the name hello_veworld
. The --no-ecto
flag tells Phoenix not to generate a database or any database-related files and the --module
flag tells Phoenix to use HelloVeWorld
as the module name for the project.
There are a few adjustments that need to be made in order pave the way for LiveView. First, we need to move to using a LiveView for our home route. We can do this by changing the base scope
in our router.ex
file to look like this:
scope "/", HelloVeWorldWeb do
pipe_through :browser
live "/", HomeLive
end
Next, we will create a new LiveView module called HomeLive
by creating a file at lib/hello_veworld_web/live/home_live.ex
with the following content:
defmodule HelloVeworldWeb.HomeLive do
use HelloVeworldWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
<div class="container">
<h1>Hello, VeWorld!</h1>
</div>
"""
end
end
With this in place you should be able to boot up your Phoenix server by running the following command:
mix phx.server --open
You should now see the "Hello, VeWorld!" message in your browser!
Installing The VeChain dApp Kit
Under normal circumstances, Phoenix handles the majority of the frontend, JavaScript-related logic for you. However, in this case, we need to interact with VeChain blockchain by way of the VeWorld browser extension, which requires us to use the VeChain dApp Kit. This means we will need to wrestle some of the control away from Phoenix write some JavaScript ourselves.
Let's start by adding the VeChain dApp Kit to our project. We can do this by creating a package.json
file in the assets
directory and adding the following content:
{
"engines": {
"node": ">=20.10.0 <22.0.0"
},
"type": "commonjs",
"dependencies": {
"@vechain/dapp-kit": "^1.0.12",
"@vechain/dapp-kit-ui": "^1.0.12",
"esbuild": "^0.19.10",
"esbuild-plugin-polyfill-node": "^0.3.0",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
}
}
Next, we need to install the dependencies by running the following command:
npm install --prefix assets
Building The JavaScript
Now that we have the VeChain dApp Kit installed, we need to create a new JavaScript file in the assets
directory called build.js
with the following content:
const esbuild = require("esbuild");
const polyfillNode = require("esbuild-plugin-polyfill-node").polyfillNode
const args = process.argv.slice(2);
const watch = args.includes('--watch');
const deploy = args.includes('--deploy');
const loader = {
// Add loaders for images/fonts/etc, e.g. { '.svg': 'file' }
};
const plugins = [
polyfillNode({
polyfills: {
crypto: true
}
})
];
// Define esbuild options
let opts = {
entryPoints: ["js/app.js"],
bundle: true,
logLevel: "info",
target: "es2017",
outdir: "../priv/static/assets",
external: ["*.css", "fonts/*", "images/*"],
loader: loader,
plugins: plugins,
define: {
"global": "window"
}
};
if (deploy) {
opts = {
...opts,
minify: true,
};
}
if (watch) {
opts = {
...opts,
sourcemap: "inline",
};
esbuild
.context(opts)
.then((ctx) => {
ctx.watch();
})
.catch((_error) => {
process.exit(1);
});
} else {
esbuild.build(opts);
}
The bulk of this file is copied directly from the Phoenix documentation on Esbuild Plugins. The only thing we've added is the polyfillNode
plugin to add the necessary polyfills and definitions for the VeChain dApp Kit.
We will follow along with that documentation and make the suggested changes to the config/dev.exs
file:
config :hello_veworld, HelloVeWorldWeb.Endpoint,
...
watchers: [
node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)],
tailwind: {Tailwind, :install_and_run, [:hello_veworld, ~w(--watch)]}
],
...
As well as the modifying the aliases in the mix.exs
file:
defp aliases do
[
setup: ["deps.get", "assets.setup", "assets.build"],
"assets.setup": ["tailwind.install --if-missing", "cmd --cd assets npm install"],
"assets.build": ["tailwind hello_veworld", "cmd --cd assets node build.js"],
"assets.deploy": [
"tailwind hello_veworld --minify",
"cmd --cd assets node build.js --deploy",
"phx.digest"
]
]
end
The main goal in the above file changes is to remove the references to Phoenix's esbuild now that we are bringing it in ourselves. Now that these changes are in place, we can run the server as normal and see all subsequent changes to the JavaScript files reflected in the browser in real time!
mix phx.server --open
VeChain dApp Kit & Websocket Configuration
Phoenix LiveView relies on Websockets to communicate between the server and the client. In order to have the VeChain dApp kit accessible to both the server and the client, we need to setup the websock to include the necessary information.
To do this, we need to follow the instructions in the VeChain dApp Kit documentation. This involves adding the following code to the assets/js/app.js
file:
...
import { DAppKitUI } from '@vechain/dapp-kit-ui'
const dAppKitOptions = {
// Required - The URL of the node to connect to
nodeUrl: "https://node-mainnet.vechain.energy",
// Optional - "main" | "test" | Connex.Thor.Block
genesis: "main",
// Optional - Defaults to false. If true, account and source will be persisted in local storage
usePersistence: true,
// Optional - Use the first available wallet
useFirstDetectedSource: false,
// Optional - Log Level - To debug the library
logLevel: "DEBUG",
}
const dappKit = DAppKitUI.configure(dAppKitOptions)
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken},
dappKit: dappKit
})
...
This code will configure the VeChain dApp Kit to connect to the VeChain mainnet and will also configure the LiveSocket to include the dAppKit object in the params.
Putting It All Together
With all of the above in place, there is only one more change that needs to be made. We need to update the file we created earlier at lib/hello_veworld_web/live/home_live.ex
to include the tag that will allow us to interact with the VeChain dApp Kit:
def render(assigns) do
~H"""
<div class="container">
<h1>Hello, VeWorld!</h1>
<vdk-button></vdk-button>
</div>
"""
end
With this change, you should see the VeChain dApp Kit button in your browser. Clicking on this button will open the VeChain dApp Kit and ask to connect with either VeWorld or Sync2. Once you've connected, you should see your wallet address reflected in the button.
Conclusion
You made it this far! Congrats! You should now have a Phoenix LiveView project that is able to interact with the VeChain blockchain using the VeChain dApp Kit. You can now build out your application as you see fit!
Some ideas for future improvements involve adding in Phoenix LiveView Hooks to allow the server to respond to changes in the client dAppKit state.
Happy Coding!
Support Kyle by sending a tip: