Poly filling buffer for Ledger Wallet

Ledger is the latest authentication method to come to LN Markets. Lucky Ledger customers can now register and login directly with their hardware wallet.

How does it work ?

The user simply has to connect his wallet to his computer, go to lnmarkets home page and click on 'Ledger' in the registration menu. A challenge will be sent to the wallet and the user will be asked to sign it. The signature and user's public key will be sent back to our servers and if they match, the user will be allowed to log in.

Discovering Ledger API

The Ledger API bridges the gap between our application and the actual device connected to the user's computer. We decided to use the @ledgerhq/hw-transport-webusb package to connect with the ledger through the WebUSB api, as well as @ledgerhq/hw-app-btc for directly interacting with Ledger's Bitcoin wallet.

Following Ledger's documentation we implemented the login logic in our application and ran into this error:

ReferenceError: Buffer is not defined

Buffer is a nodejs type used to easily manipulate raw bytes. It does not exist in the browser, which is why it has to be polyfilled. Polyfilling is a technique that will provide the missing dependencies and make them available where they are needed.

In the example linked above, the core-js package did this for the Ledger packages, but it did not work for us. Fortunately, other people apparently stumbled upon the same problem and found the solution:

Using Node.js builtin modules with Vite
Vite is a new tool that helps to jumpstart and streamline frontend Javascript development. The main advantage of Vite against other…

After updating our vite configuration file by following the post above, the Buffer dependency was polyfilled as it should have been and Ledger's packages worked in the context of our application. Unfortunately, this was only true for our development server. In production, when actually building the application, the same error popped again.

This is because Vite uses two different module bundlers, one for the development server and one for production. Hence, the three different polyfilling packages used in the configuration above.

The rollup-plugin-node-polyfills package seemed to be at the root of our problem. As the name indicates, it is the package actually performing the polyfilling for the rollup bundler. After a long debugging session, involving the creation of a smaller vue project using the exact same dependencies, the definitive solution was finally found.

The solution

In the smaller project, the login function worked as expected in both development mode and in production. In other words, the Buffer dependency was correctly polyfilled in both cases.

The key difference was in the structure of the projects. In our application we use pnpm as our packet manager as opposed to npm in the post above. Since we have multiple packages in our application, we use pnpm's workspace feature. Basically, the point is to centralize some common dependencies to reduce disk space usage. This does not change anything except node_modules' internal structure.

This is precisely why the rollupNodePolyfill() function failed to do as advertised. When looking at the package's source, we could see that when the function was expecting the node_modules directory structure to be the same as if we were using npm.

All we had to do then was to pass the include option to the polyfill function and to set it at a value that was not undefined. Simply passing an empty string did the trick. The function was now making the Buffer type available everywhere.

Here is the final version of our vite.config.js file:‌

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'
import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill'
import rollupNodePolyfill from 'rollup-plugin-node-polyfills'

export default defineConfig({
    plugins: [vue()],
    resolve: {
        alias: {
            buffer: 'rollup-plugin-node-polyfills/polyfills/buffer-es6',
        }
    },
    optimizeDeps: {
        esbuildOptions: {
            plugins: [
                NodeGlobalsPolyfillPlugin({ buffer: true }),
                NodeModulesPolyfillPlugin()
            ]
        }
    },
    build: {
        rollupOptions: {
            plugins: [rollupNodePolyfill({ include: '' })],
        }
    }
})
vite.config.js

The solution (without polyfill packages)

We noticed that there are two rollup-plugin-polyfill-node and rollup-plugin-node-polyfills, one is maintained, and the other one is deprecated but at a lot of Stack Overflow answer are referencing the last one.

After searching for a more elegant solution (and being fed up with deprecation notice from several packages), we came with this:

  • Install buffer as dependency in the front application

For dev setup (i.e. when using vite serve --mode development) we just need to add this to the src/main.js file.

import { Buffer } from 'buffer'

// Ensure global is defined
window.global = window

/**
 * Polyfill the buffer module for @ledgerhq/**
 * This only work for live server, we need to use @rollup/plugin-inject for the build version
 */
window.Buffer = Buffer
src/main.js

This sort the issue for dev version.

For the production version, we need to install @rollup/plugin-inject this plugin allows us to polyfill any package.

To use it, update your vite.config.js file like this :

import inject from '@rollup/plugin-inject'
import { defineConfig } from 'vite'

export default defineConfig({
  // Your app specific configuration
  build: {
    rollupOptions: {
      plugins: [
        inject({
          include: ['node_modules/@ledgerhq/**'],
          modules: { Buffer: ['buffer', 'Buffer'] },
        }),
      ],
    },
  },
})
vite.config.js

And voila !

I hope this post will help some people trying to polyfill buffer or use ledger package with Vite !

Sources :

Privacy Policy