/solchat-sdk

Primary LanguageTypeScript

Solchat SDK v1.0

We have introduced this TypeScript SDK to create a chat feature for Solana dApps. This SDK is built with the help of Metaplex Kinobi.

Chat Programs

Network Address
Mainnet 3RwiUiCrxqsnbSBkbqAdRCY3n9RdwJHG9MVeSmtG7PSs
Devnet 5zf6JLJTTHobHboH7X17k5x3TeACiziomehjAGRx6PYq

Install

yarn
npm install -g typescript
npm install -g ts-node

Depending on the Solana network, change the RPC in index.ts:

const umi = createUmi("http://localhost:8880", {
  commitment: "processed",
});

Replace the chat program address in src/idls/solchat.json, address field, and run generate:

"metadata": {
  "address": "3RwiUiCrxqsnbSBkbqAdRCY3n9RdwJHG9MVeSmtG7PSs",
  "origin": "anchor"
}

Generate TypeScript definitions:

ts-node generate.ts

Once all the setup is done, you can integrate all the functions from this SDK with your app. Example functions are provided in index.ts:

import {
  createSignerFromKeypair,
  keypairIdentity,
  PublicKey,
  publicKeyBytes,
  Signer,
  sol,
  Umi,
} from "@metaplex-foundation/umi";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import {
  acceptChat,
  addUserToGroup,
  createGroup,
  fetchAllGroup,
  fetchAllUser,
  fetchGroup,
  fetchUser,
  initializeUser,
  leaveChat,
  sendMessage,
  SOLCHAT_PROGRAM_ID,
  upgradeGroupVersion,
} from "./clients/ts/src/generated";
import {
  addPublicKeyPrefixAndSuffix,
  decryptWithAES,
  encryptWithAES,
  generateRandomU64,
  generateRsaKeypairFromSeed,
  getGroupVersion,
  getMemberIndex,
  getUserRsaKeypair,
  stripPrefixAndSuffix,
} from "./utils";
import crypto from "crypto";
import { base58 } from "@metaplex-foundation/umi/serializers";
import type { Option } from "@metaplex-foundation/umi";
import {
  addUser,
  createGroupWithUser,
  fetchDecryptedMessages,
  getGroupPda,
  getUserPda,
  sendMessageToGroup,
  upgradeGroupEncryption,
} from "./solchatHelpers";

const umi = createUmi("http://localhost:8899", {
  commitment: "processed",
});
umi.use(keypairIdentity(umi.eddsa.generateKeypair(), true));

(async () => {
  await umi.rpc.airdrop(umi.identity.publicKey, sol(1));
  const users = [
    umi.eddsa.generateKeypair(),
    umi.eddsa.generateKeypair(),
    umi.eddsa.generateKeypair(),
  ];

  for (const user of users) {
    const signer = createSignerFromKeypair(umi, user);

    await umi.rpc.airdrop(user.publicKey, sol(1));

    const keypair = await getUserRsaKeypair(signer);

    const txn = await initializeUser(umi, {
      username: `user-${user.publicKey.slice(0, 5)}`,
      rsaPublicKey: [
        ...Buffer.from(stripPrefixAndSuffix(keypair.rsaPublicKey)),
      ],
      owner: signer,
      user: getUserPda(umi, user.publicKey),
    }).sendAndConfirm(umi);

    console.log("Create User", base58.deserialize(txn.signature));
  }

  // Send a message from user1

  const groupId = await createGroupWithUser(
    umi,
    createSignerFromKeypair(umi, users[0]),
    users[1].publicKey,
    false // Just change this to true for DMs, makes it so you can't add or kick people
  );

  await acceptChat(umi, {
    //Only used for UI
    group: getGroupPda(umi, groupId)[0],
    signer: createSignerFromKeypair(umi, users[1]),
    userAcc: getUserPda(umi, users[1].publicKey),
  }).sendAndConfirm(umi);

  await sendMessageToGroup(
    umi,
    groupId,
    createSignerFromKeypair(umi, users[0]),
    "Hello, World!"
  );

  // Fetch decrypted messages from user2
  console.log(
    (
      await fetchDecryptedMessages(
        umi,
        groupId,
        createSignerFromKeypair(umi, users[1])
      )
    ).map((message) => message.message)
  );

  // Skip this cause it's not required for beta
  /*
  console.log("Upgrade group encryption");

  await upgradeGroupEncryption(
    // Not required and doesn't work for groups above like 6 people, this is only used to make previous messages unreadable by new members
    umi,
    getGroupPda(umi, groupId)[0],
    createSignerFromKeypair(umi, users[0])
  );
  */

  // Add user3 to the group
  console.log("Add user3 to the group");

  await addUser(
    umi,
    getGroupPda(umi, groupId)[0],
    createSignerFromKeypair(umi, users[0]),
    users[2].publicKey
  );

  await acceptChat(umi, {
    group: getGroupPda(umi, groupId)[0],
    signer: createSignerFromKeypair(umi, users[2]),
    userAcc: getUserPda(umi, users[2].publicKey),
  }).sendAndConfirm(umi);

  // User 2 sends a message

  await sendMessageToGroup(
    umi,
    groupId,
    createSignerFromKeypair(umi, users[1]),
    "Hello, User 3!"
  );

  console.log("Fetch all groups from user3");

  const user3 = await fetchUser(umi, getUserPda(umi, users[2].publicKey));

  const groups = await fetchAllGroup(umi, user3.acceptedChats);

  console.log(groups);

  console.log("Fetch decrypted messages from user3");
  // Fetch decrypted messages from user3

  const groupId1 = Buffer.alloc(8); // 8 bytes for u64
  groupId1.writeBigUInt64BE(groups[0].groupId);

  console.log(
    (
      await fetchDecryptedMessages(
        umi,
        groupId1,
        createSignerFromKeypair(umi, users[2])
      )
    ).map((message) => message.message)
  );

  await leaveChat(umi, {
    // UI only, leave with user 2, doesn't actually do anything except modify the accepted chats array
    group: getGroupPda(umi, groupId)[0],
    signer: createSignerFromKeypair(umi, users[1]),
    userAcc: getUserPda(umi, users[1].publicKey),
  }).sendAndConfirm(umi);
})();