/Blog/integrating-vechain-and-phoenix


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:


by Kyle