How to integrate LIFI Widget on your NextJS app

How to integrate LIFI Widget on your NextJS app

Imagine seamlessly integrating the power of LIFI, a cutting-edge multi-chain liquidity aggregation protocol, is added to your Next.js application. LIFI facilitates any-to-any swaps by consolidating bridges and decentralized exchange (DEX) aggregators across over 20 networks. In this article, we'll explore the process of integrating the LIFI protocol into your Next.js app, unlocking a world of possibilities for frictionless cryptocurrency trading and enhanced liquidity management.

Introduction

Introducing LIFI:

LIFI is a multi-chain liquidity aggregation protocol that supports any-2-any swaps by aggregating bridges and DEX aggregators across +20 networks. You can see it as a super-intelligent system that gathers all the places where people exchange money, like bringing together all the shops in a big marketplace. It helps people trade their money from one type to another, making it easy to switch between different currencies. So, instead of going to many other shops, LIFI brings them all to one place, making it simple and convenient for people to exchange money.

LIFI Widget:

The LIFI Widget comprises ready-made user interface elements seamlessly incorporating a secure cross-chain bridging and swapping feature into your web application. These components can be customized to align perfectly with your app's design, enhancing its visual appeal. By integrating the LIFI Widget, you can bolster your multi-chain strategy and draw in users from various locations, providing a user-friendly and secure experience.

Let’s build our app together

Setting up your Next.js app:

You can begin setting up your NextJS application using these guidelines:

  • Installation: Start by installing Next.js using npm or yarn. Run the following command in your terminal:
npx create-next-app@latest
  • Project Structure: Next.js follows a convention-based file structure. Familiarize yourself with the default project structure, including pages, public, and style directories.

Here's how your folder structure would look like after installation of NextJS:

We have successfully set up NextJS for our integration.

Installing libraries

We have to install LIFI libraries in our NextJS app to access its functionality in our web app. Use npm or yarn to install the LIFI Widget library. Run the following command in your terminal:

npm install --save @lifi/widget

Or for those who use Yarn,

yarn add @lifi/widget @lifi/sdk

This command will install the LIFI Widget package as a dependency on your project.

By installing the LIFI Widget library, you'll gain access to pre-built UI components and functionality that enable secure cross-chain bridging and swapping within your Next.js application. This installation process sets the stage for integrating LIFI's powerful features seamlessly into your project.

Integrating the Widget

In your /app directory, create a new folder named components. After which we'll create our widget component named Widget.tsx and paste this code there:

'use client';

import type { WidgetConfig } from '@lifi/widget';
import { LiFiWidget, WidgetSkeleton } from '@lifi/widget';
import { ClientOnly } from './ClientOnly';

export function Widget() {
  const config = {
    appearance: 'light',
    theme: {
      container: {
        boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
        borderRadius: '16px',
      },
    },
  } as Partial<WidgetConfig>;

  return (
    <main>
      <ClientOnly fallback={<WidgetSkeleton config={config} />}>
        <LiFiWidget config={config} integrator="nextjs-example" />
      </ClientOnly>
    </main>
  );
}

I am sure you're wondering why we have other components imported into our Widget component now; here is why: in modern web development, ensuring a smooth user experience is paramount. The ClientOnly Component serves a crucial role by selectively rendering content only after client-side JavaScript has fully loaded.

So, on that note, let's create a new component named ClientOnly.tsx

import { type PropsWithChildren } from 'react';
import { useHydrated } from '../hooks/useHydrated';

interface ClientOnlyProps extends PropsWithChildren {
  fallback?: React.ReactNode;
}

/**
 * Render the children only after the JS has loaded client-side. Use an optional
 * fallback component if the JS is not yet loaded.
 */
export function ClientOnly({ children, fallback = null }: ClientOnlyProps) {
  const hydrated = useHydrated();
  return hydrated ? children : fallback;
}

We also have the WidgetEvents.tsx component that offers real-time monitoring of asset transfers. It tracks key execution stages and potential errors, ensuring a smooth user experience.
Let's create our WidgetEvents.tsx component

'use client';
import type { Route } from '@lifi/sdk';
import type {
  RouteExecutionUpdate,
  RouteHighValueLossUpdate,
} from '@lifi/widget';
import { WidgetEvent, useWidgetEvents } from '@lifi/widget';
import { useEffect } from 'react';

export const WidgetEvents = () => {
  const widgetEvents = useWidgetEvents();

  useEffect(() => {
    const onRouteExecutionStarted = (route: Route) => {
      console.log('onRouteExecutionStarted fired.');
    };
    const onRouteExecutionUpdated = (update: RouteExecutionUpdate) => {
      console.log('onRouteExecutionUpdated fired.');
    };
    const onRouteExecutionCompleted = (route: Route) => {
      console.log('onRouteExecutionCompleted fired.');
    };
    const onRouteExecutionFailed = (update: RouteExecutionUpdate) => {
      console.log('onRouteExecutionFailed fired.');
    };
    const onRouteHighValueLoss = (update: RouteHighValueLossUpdate) => {
      console.log('onRouteHighValueLoss continued.');
    };
    widgetEvents.on(WidgetEvent.RouteExecutionStarted, onRouteExecutionStarted);
    widgetEvents.on(WidgetEvent.RouteExecutionUpdated, onRouteExecutionUpdated);
    widgetEvents.on(
      WidgetEvent.RouteExecutionCompleted,
      onRouteExecutionCompleted,
    );
    widgetEvents.on(WidgetEvent.RouteHighValueLoss, onRouteHighValueLoss);
    widgetEvents.on(WidgetEvent.RouteExecutionFailed, onRouteExecutionFailed);
    return () => widgetEvents.all.clear();
  }, [widgetEvents]);

  return null;
};

Let's do something. How do we check if Javascript has finished loading on the client side of our app? Let's write code that handles that.

In your /app directory, create a new folder named hooks and inside it, a file named useHydrated.tsx and paste the following code into it

import { useSyncExternalStore } from 'react';

function subscribe() {
  return () => {};
}

/**
 * Return a boolean indicating if the JS has been hydrated already.
 * When doing Server-Side Rendering, the result will always be false.
 * When doing Client-Side Rendering, the result will always be false on the
 * first render and true from then on. Even if a new component renders it will
 * always start with true.
 */
export function useHydrated() {
  return useSyncExternalStore(
    subscribe,
    () => true,
    () => false,
  );
}

This code lets us check if the JavaScript has finished loading on the client side in our React app. It uses a hook called useHydrated. When you use it, it'll return false during server-side rendering, but once the JavaScript is loaded on the client, it'll switch to true and stay that way.

If you have reached this point, you might want to grab a glass of water. If you did this correctly, your folder structure should look like this:

Now, let's edit our page.tsx file in the /app Directory. Replace the existing code there with this:

import { Widget } from './components/Widget';
import { WidgetEvents } from './components/WidgetEvents';

export default function Home() {
  return (
    <main>
      <WidgetEvents />
      <Widget />
    </main>
  );
}

Open your terminal again and use this command to run your server.

cd lifi && yarn dev

From my own terminal, it seems like everything is running smooth, If yours doesn't run smoothly, edit your next.config.mjs file with this piece of code

/** @type {import('next').NextConfig} */
const nextConfig = {
    webpack: (config) => {
      config.resolve.fallback = { fs: false, net: false, tls: false };
      config.externals.push('pino-pretty', 'lokijs', 'encoding');
      // config.externals.push('pino-pretty');
      return config;
    },
  };

  export default nextConfig;

Now head on to your browser and visit localhost:3000

Conclusion

Now, we have our fully functional widget fully integrated in our NextJS App. I f you have any questions or come across any bugs while following this article? Reach out to me on: