Skip to content

Gated group

How to create a gated group chat through an admin agent.

Create the group

Send this message to the bot to kickstart the creation of the group.

create group

The bot will create a private group where you and the bot are the admins.Then will provide some information like:

Group created!
- ID: {groupId}
- Group Frame URL: https://converse.xyz/group/{groupId}:
- This url will deelink to the group inside Converse
- Once in the other group you can share the invite with your friends.

Start the server

The code for the server is the following:

src/index.ts
export const agent = createAgent({
  name: "Gated Group Creator Agent",
  tag: "@bot",
  description: "A gated group creator agent.",
  intro: "You are a gated group creator agent.",
  skills: [gated],
}).run();

Endpoint

Once you start the server on your port 3000 by default you can ping this endpoint with the parameters

curl -X POST http://localhost:3000/add-wallet \
 -H "Content-Type: application/json" \
 -d '{"walletAddress": "0x93E2fc3e99dFb1238eB9e0eF2580EFC5809C7204", "groupId": "b9ab876c87ef3cf63b81c8d45c824fae"}'

Skill

The code for the skill is the following:

src/skills/gated.ts
import { Context, Skill } from "@xmtp/message-kit";
import express from "express";
import { Client } from "@xmtp/node-sdk";
import { checkNft } from "../plugins/alchemy.js";
 
export const gated: Skill[] = [
  {
    skill: "create",
    examples: ["/create"],
    handler: handler,
    adminOnly: true,
    description: "Create a new group.",
  },
];
 
async function handler(context: Context) {
  const {
    message: {
      sender,
      content: { skill },
    },
    xmtp,
  } = context;
 
  if (skill === "create") {
    const group = await createGroup(xmtp.client, sender.address, xmtp.address);
 
    await context.send({
      message: `Group created!\n- ID: ${group?.id}\n- Group Frame URL: https://converse.xyz/group/${group?.id}: \n- This url will deelink to the group inside Converse\n- Once in the other group you can share the invite with your friends.`,
      originalMessage: context.message,
    });
    return;
  } else {
    await context.send({
      message:
        "👋 Welcome to the Gated Bot Group!\nTo get started, type /create to set up a new group. 🚀\nThis example will check if the user has a particular nft and add them to the group if they do.\nOnce your group is created, you'll receive a unique Group ID and URL.\nShare the URL with friends to invite them to join your group!",
      originalMessage: context.message,
    });
  }
}
 
export function startGatedGroupServer(client: Client) {
  async function addWalletToGroup(
    walletAddress: string,
    groupId: string,
  ): Promise<string> {
    const verified = await checkNft(walletAddress, "XMTPeople");
    if (!verified) {
      console.log("User cant be added to the group");
      return "not verified";
    } else {
      try {
        await addToGroup(groupId, client, walletAddress);
        return "success";
      } catch (error: any) {
        console.log(error.message);
        return "error";
      }
    }
  }
 
  // Endpoint to add wallet address to a group from an external source
  const app = express();
  app.use(express.json());
  app.post("/add-wallet", async (req, res) => {
    try {
      const { walletAddress, groupId } = req.body;
      const result = await addWalletToGroup(walletAddress, groupId);
      res.status(200).send(result);
    } catch (error: any) {
      res.status(400).send(error.message);
    }
  });
  // Start the servfalcheer
  const PORT = process.env.PORT || 3000;
  const url = process.env.URL || `http://localhost:${PORT}`;
  app.listen(PORT, () => {
    console.warn(
      `Use this endpoint to add a wallet to a group indicated by the groupId\n${url}/add-wallet <body: {walletAddress, groupId}>`,
    );
  });
}
 
export async function createGroup(
  client: Client | undefined,
  senderAddress: string | undefined,
  clientAddress: string | undefined,
) {
  try {
    let senderInboxId = "";
    await client?.conversations.sync();
    const group = await client?.conversations.newGroup([
      senderAddress ?? "",
      clientAddress ?? "",
    ]);
    console.log("Group created", group?.id);
    const members = await group?.members();
    const senderMember = members?.find((member) =>
      member.accountAddresses.includes(senderAddress?.toLowerCase() ?? ""),
    );
    if (senderMember) {
      senderInboxId = senderMember.inboxId;
      console.log("Sender's inboxId:", senderInboxId);
    } else {
      console.log("Sender not found in members list");
    }
    await group?.addSuperAdmin(senderInboxId);
    console.log(
      "Sender is superAdmin",
      await group?.isSuperAdmin(senderInboxId),
    );
    await group?.send(`Welcome to the new group!`);
    await group?.send(`You are now the admin of this group as well as the bot`);
    return group;
  } catch (error) {
    console.log("Error creating group", error);
    return undefined;
  }
}
 
export async function removeFromGroup(
  groupId: string,
  client: Client,
  senderAddress: string,
): Promise<{ code: number; message: string }> {
  try {
    let lowerAddress = senderAddress.toLowerCase();
    const isOnXMTP = await client.canMessage([lowerAddress]);
    if (!isOnXMTP)
      return {
        code: 400,
        message: "You don't seem to have a XMTP identity ",
      };
    const conversation =
      await client.conversations.getConversationById(groupId);
    console.warn("removing from group", conversation?.id);
    await conversation?.sync();
    await conversation?.removeMembers([lowerAddress]);
    console.warn("Removed member from group");
    await conversation?.sync();
    const members = await conversation?.members();
    console.warn("Number of members", members?.length);
 
    let wasRemoved = true;
    if (members) {
      for (const member of members) {
        let lowerMemberAddress = member.accountAddresses[0].toLowerCase();
        if (lowerMemberAddress === lowerAddress) {
          wasRemoved = false;
          break;
        }
      }
    }
    return {
      code: wasRemoved ? 200 : 400,
      message: wasRemoved
        ? "You have been removed from the group"
        : "Failed to remove from group",
    };
  } catch (error) {
    console.log("Error removing from group", error);
    return {
      code: 400,
      message: "Failed to remove from group",
    };
  }
}
 
export async function addToGroup(
  groupId: string,
  client: Client,
  address: string,
  asAdmin: boolean = false,
): Promise<{ code: number; message: string }> {
  try {
    let lowerAddress = address.toLowerCase();
    const isOnXMTP = await client.canMessage([lowerAddress]);
    if (!isOnXMTP)
      return {
        code: 400,
        message: "You don't seem to have a XMTP identity ",
      };
    const group = await client.conversations.getConversationById(groupId);
    console.warn("Adding to group", group?.id);
    await group?.sync();
    await group?.addMembers([lowerAddress]);
    console.warn("Added member to group");
    await group?.sync();
    if (asAdmin) {
      await group?.addSuperAdmin(lowerAddress);
    }
    const members = await group?.members();
    console.warn("Number of members", members?.length);
 
    if (members) {
      for (const member of members) {
        let lowerMemberAddress = member.accountAddresses[0].toLowerCase();
        if (lowerMemberAddress === lowerAddress) {
          console.warn("Member exists", lowerMemberAddress);
          return {
            code: 200,
            message: "You have been added to the group",
          };
        }
      }
    }
    return {
      code: 400,
      message: "Failed to add to group",
    };
  } catch (error) {
    return {
      code: 400,
      message: "Failed to add to group",
    };
  }
}