How to integrate wallet-to-wallet chat (Solana Edition)

How to integrate wallet-to-wallet chat (Solana Edition)

We chat using SMS, emails, and communication apps such as Discord, Telegram, and WhatsApp. Our contact info consists of our phone number, email address, and username on our preferred apps.

The blockchain has introduced a wallet address as a new contact detail. To use the blockchain, one must create a digital wallet that is assigned a "wallet address," or a series of letters and numbers that acts as an identifier.

Chatting between wallet addresses enables easier and more efficient communication between users of the blockchain. Since a wallet address is not associated with other contact information, blockchain users can communicate without needing to expose their other contact details. Use cases include:

  • Notifications on community events for token holders

  • Requests to trade tokens and NFTs

Dialect Labs has introduced an easy-to-use SDK to enable wallet-to-wallet chat into a front-end application. This tutorial focuses on creating a wallet-to-wallet chat between the signed-in user and another wallet address using the Dialect SDK on the Solana chain on React.

Tutorial

Pre-Requisites

Install packages

npm

npm install @dialectlabs/sdk --save
npm install @dialectlabs/blockchain-sdk-solana --save
npm install @dialectlabs/react-ui --save
npm install @dialectlabs/react-sdk-blockchain-solana --save

yarn

yarn add @dialectlabs/sdk
yarn add @dialectlabs/blockchain-sdk-solana
yarn add @dialectlabs/react-ui
yarn add @dialectlabs/react-sdk-blockchain-solana

Create a utility function to handle connected Solana wallet

You must create a utility function that handles the connected Solana information in a format that the Dialect SDK can read. You will use this utility function when calling the Dialect provider.

In a separate .tsx file (for example, named util/SolanaToDialect.tsx):

import { DialectSolanaWalletAdapter } from '@dialectlabs/react-sdk-blockchain-solana';
import { WalletContextState } from '@solana/wallet-adapter-react';

export const solanaWalletToDialectWallet = (
  wallet: WalletContextState
): DialectSolanaWalletAdapter | null => {
  if (
    !wallet.connected ||
    wallet.connecting ||
    wallet.disconnecting ||
    !wallet.publicKey
  ) {
   return null
  }

  return {
    publicKey: wallet.publicKey!,
    signMessage: wallet.signMessage,
    signTransaction: wallet.signTransaction,
    signAllTransactions: wallet.signAllTransactions,
    // @ts-ignore
    diffieHellman: wallet.wallet?.adapter?._wallet?.diffieHellman
      ? async (pubKey: any) => {
          // @ts-ignore
          return wallet.wallet?.adapter?._wallet?.diffieHellman(pubKey);
        }
      : undefined,
  };
};

Create Dialect Provider

You must create a Dialect SDK provider and wrap it around your entire React application. You will also now utilize the utility function in the last step.

In your _app.tsx or file where you store your main application, add the following:

import { 
  DialectUiManagementProvider, 
  DialectThemeProvider, 
  DialectNoBlockchainSdk, 
  ConfigProps
} from '@dialectlabs/react-ui';
import { DialectSolanaSdk, DialectSolanaWalletAdapter, SolanaConfigProps } from '@dialectlabs/react-sdk-blockchain-solana';
import { useEffect, useMemo, useState } from "react";
import { solanaWalletToDialectWallet } from "@/util/SolanaToDialect";

export default function App({ Component, pageProps }: AppProps) {
  const SdkProvider = ({children} : React.PropsWithChildren<{}>) => {
    const solanaWallet = useWallet();
    const [dialectSolanaWalletAdapter, setDialectSolanaWalletAdapter] =
    useState<DialectSolanaWalletAdapter | null>(null);

  const dialectConfig: ConfigProps = useMemo(() => ({
    environment: 'development',
    dialectCloud: {
      tokenStore: 'local-storage',
    }
  }), []);

  const solanaConfig: SolanaConfigProps = useMemo(() => ({
    wallet: dialectSolanaWalletAdapter,
  }), [dialectSolanaWalletAdapter]);

  useEffect(() => {
    setDialectSolanaWalletAdapter(solanaWalletToDialectWallet(solanaWallet));
  }, [solanaWallet]);

  if (dialectSolanaWalletAdapter) {
    return (
      <DialectSolanaSdk config={dialectConfig} solanaConfig={solanaConfig}>
        {children}
      </DialectSolanaSdk>
    );
  }

  return <DialectNoBlockchainSdk>{children}</DialectNoBlockchainSdk>;
  }

  const DialectProviders = ({children} : React.PropsWithChildren<{}>) => {
    return (
      <SdkProvider>
        <DialectThemeProvider>
          <DialectUiManagementProvider>
            {children}
          </DialectUiManagementProvider>
        </DialectThemeProvider>
      </SdkProvider>
    );
  }

Wrap Dialect Provider

Wrap the provider created above within the return statement in the _app.tsx or file that you store your main application. For example:

return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider>
        <DialectProviders>
        <Layout>
          <Component {...pageProps} />
        </Layout>
        </DialectProviders>
      </WalletProvider>
    </ConnectionProvider>
 );

Full Example of an _app.tsx:

import type { AppProps } from "next/app";
import { ConnectionProvider, useWallet } from "@solana/wallet-adapter-react";
import dynamic from "next/dynamic";
import { 
  DialectUiManagementProvider, 
  DialectThemeProvider, 
  DialectNoBlockchainSdk, 
  ConfigProps
} from '@dialectlabs/react-ui';
import { DialectSolanaSdk, DialectSolanaWalletAdapter, SolanaConfigProps } from '@dialectlabs/react-sdk-blockchain-solana';
import Layout from "@/components/layout/Layout";
import React, { useEffect, useMemo, useState } from "react";
import { solanaWalletToDialectWallet } from "@/util/SolanaToDialect";
import "@/styles/globals.css";

export default function App({ Component, pageProps }: AppProps) {
  const endpoint = "https://rpc.ankr.com/solana";

  const WalletProvider = dynamic(
    () => import("../contexts/ClientWalletProvider"),
    {
      ssr: false,
    }
  );

  const SdkProvider = ({children} : React.PropsWithChildren<{}>) => {
    const solanaWallet = useWallet();

    const [dialectSolanaWalletAdapter, setDialectSolanaWalletAdapter] =
    useState<DialectSolanaWalletAdapter | null>(null);

  const dialectConfig: ConfigProps = useMemo(() => ({
    environment: 'development',
    dialectCloud: {
      tokenStore: 'local-storage',
    }
  }), []);

  const solanaConfig: SolanaConfigProps = useMemo(() => ({
    wallet: dialectSolanaWalletAdapter,
  }), [dialectSolanaWalletAdapter]);

  useEffect(() => {
    setDialectSolanaWalletAdapter(solanaWalletToDialectWallet(solanaWallet));
  }, [solanaWallet]);

  if (dialectSolanaWalletAdapter) {
    return (
      <DialectSolanaSdk config={dialectConfig} solanaConfig={solanaConfig}>
        {children}
      </DialectSolanaSdk>
    );
  }

  return <DialectNoBlockchainSdk>{children}</DialectNoBlockchainSdk>;
  }

  const DialectProviders = ({children} : React.PropsWithChildren<{}>) => {
    return (
      <SdkProvider>
        <DialectThemeProvider>
          <DialectUiManagementProvider>
            {children}
          </DialectUiManagementProvider>
        </DialectThemeProvider>
      </SdkProvider>
    );
  }

  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider>
        <DialectProviders>
        <Layout>
          <Component {...pageProps} />
        </Layout>
        </DialectProviders>
      </WalletProvider>
    </ConnectionProvider>
  );
}

Create a Solana messaging wallet key pair

You will need to create a Solana messaging wallet key pair and pass it into DIALECT_SDK_CREDENTIALS in your .env file.

Please ensure that you have the Solana CLI set up.

Generate the key pair using these commands:

export your_path=~/projects/dialect/keypairs/
solana-keygen new --outfile ${your_path}/monitor-localnet-keypair.private
solana-keygen pubkey ${your_path}/monitor-localnet-keypair.private > ${your_path}/monitor-localnet-keypair.public

The contents of this file is the value to DIALECT_SDK_CREDENTIALS that you will put in your .env.

You can either pass in the file or the contents within the file in your .env. As an example:

DIALECT_SDK_CREDENTIALS=cat monitor-localnet-keypair.private ts-node ./000.1-solana-monitoring-service-server.ts

//or 

DIALECT_SDK_CREDENTIALS=[45, 45....] //rest of the key pair

Congratulations on making it this far!

You can now use any of the utility functions developed by Dialect to integrate wallet-to-wallet features in your React app. Let's proceed to creating and showing a conversation.

Add Dialect's UI

You can add Dialect's pre-developed UI messaging component that allows users to create a thread and send messages to a wallet address.

In the React component that you would like to render Dialect's UI, add the following:

import { BottomChat } from '@dialectlabs/react-ui';
import '@dialectlabs/react-ui/index.css';

export default function Chat() {
   return (
    <>
   <BottomChat dialectId="dialect-bottom-chat" pollingInterval={300}/>     //pollingInterval is the time in miliseconds that the SDK will "watch" for new messages and is optional
    </>
   ) 
}

As an alternative, you can create your UI. For demo purposes, let's create a button to create a chat thread.

Use your UI: Create a Message

Instantiate or "Start" the SDK

You can instantiate or "start" the SDK. You must pass in the development environment. It is recommended to use the "development" environment for testing before using the "production" environment on the live version of your app.

import {
  CreateThreadCommand,
  Dialect,
  DialectCloudEnvironment,
  DialectSdk,
  ThreadMemberScope,
} from "@dialectlabs/sdk";
import { Solana, SolanaSdkFactory } from "@dialectlabs/blockchain-sdk-solana";
import { solanaWalletToDialectWallet } from "@/util/SolanaToDialect";
import { useWallet } from "@solana/wallet-adapter-react";

export default function Component() {
   const solanaWallet = useWallet();

   const environment: DialectCloudEnvironment = "development";

   const sdk: DialectSdk<Solana> | undefined = useMemo(() => {
      const solanaToDialect = solanaWalletToDialectWallet(solanaWallet);
        if (!solanaToDialect) return;
        return Dialect.sdk(
      {
        environment,
      },
      SolanaSdkFactory.create({
        wallet: solanaToDialect,
      })
      );
      }, [solanaWallet]);
    }

Create a Button to Start a Conversation

For example, you can create a button that runs Dialect's Create Thread utility function upon clicking.

import {
  CreateThreadCommand,
  Dialect,
  DialectCloudEnvironment,
  DialectSdk,
  ThreadMemberScope,
} from "@dialectlabs/sdk";
import React from "react";
import { Solana, SolanaSdkFactory } from "@dialectlabs/blockchain-sdk-solana";
import { solanaWalletToDialectWallet } from "@/util/SolanaToDialect";
import { useWallet } from "@solana/wallet-adapter-react";

export default function ChatButton() {
  const environment: DialectCloudEnvironment = "development";

  const sdk: DialectSdk<Solana> | undefined = useMemo(() => {
    const solanaToDialect = solanaWalletToDialectWallet(solanaWallet);
    if (!solanaToDialect) return;
    return Dialect.sdk(
      {
        environment,
      },
      SolanaSdkFactory.create({
        wallet: solanaToDialect,
      })
    );
  }, [solanaWallet]);

   async function createThread(recipient: string) {
    const command: CreateThreadCommand = {
      encrypted: false,
      me: {
        scopes: [ThreadMemberScope.ADMIN, ThreadMemberScope.WRITE],
      },
      otherMembers: [
        {
          address: recipient,
          scopes: [ThreadMemberScope.ADMIN, ThreadMemberScope.WRITE],
        },
      ],
    };

    try {
      const thread = await sdk?.threads.create(command);
      return thread;
    } catch (e) {
      alert(`${e}`);
      console.log("Error:", e);
    }
  }
  return (
        <button onClick={() => createThread('thewalletaddress')} className="bg-purple-300 hover:bg-purple-400 text-white font-bold py-2 px-4 rounded w-64">
          Create Chat
        </button>
      </a>
    </div>
  );
}

You're all done!

You have now integrated wallet-to-wallet chat in your React app.

If you are building out your UI, you can find Dialect's other exposed utility functions in their documentation.

To learn more about Dialect, visit them at https://www.dialect.to/.