create-message-kit 1.1.9-beta.1 → 1.1.9-beta.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ import { getUserInfo, XMTPContext } from "@xmtp/message-kit";
2
+ import type { skillAction } from "@xmtp/message-kit";
3
+ export const registerSkill: skillAction[] = [
4
+ {
5
+ skill: "/pay [amount] [token] [username]",
6
+ examples: ["/pay 10 usdc vitalik.eth", "/pay 1 @alix"],
7
+ description:
8
+ "Send a specified amount of a cryptocurrency to a destination address.",
9
+ handler: handlePay,
10
+ params: {
11
+ amount: {
12
+ default: 10,
13
+ type: "number",
14
+ },
15
+ token: {
16
+ default: "usdc",
17
+ type: "string",
18
+ values: ["eth", "dai", "usdc", "degen"], // Accepted tokens
19
+ },
20
+ username: {
21
+ default: "",
22
+ type: "username",
23
+ },
24
+ },
25
+ },
26
+ ];
27
+
28
+ export async function handlePay(context: XMTPContext) {
29
+ const {
30
+ message: {
31
+ content: { params },
32
+ },
33
+ } = context;
34
+ const txpayUrl = "https://txpay.vercel.app";
35
+
36
+ const { amount: amountSend, token: tokenSend, username } = params;
37
+ let senderInfo = await getUserInfo(username);
38
+ if (!amountSend || !tokenSend || !senderInfo) {
39
+ context.reply(
40
+ "Missing required parameters. Please provide amount, token, and username.",
41
+ );
42
+ return {
43
+ code: 400,
44
+ message:
45
+ "Missing required parameters. Please provide amount, token, and username.",
46
+ };
47
+ }
48
+
49
+ let sendUrl = `${txpayUrl}/?&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
50
+ await context.send(`${sendUrl}`);
51
+ }
@@ -0,0 +1,61 @@
1
+ import { XMTPContext, AbstractedMember } from "@xmtp/message-kit";
2
+ import { getUserInfo } from "@xmtp/message-kit";
3
+ import type { skillAction } from "@xmtp/message-kit";
4
+
5
+ export const registerSkill: skillAction[] = [
6
+ {
7
+ skill: "/tip [usernames] [amount] [token]",
8
+ examples: ["/tip @vitalik 10 usdc"],
9
+ description: "Tip users in a specified token.",
10
+ handler: handleTipping,
11
+ params: {
12
+ username: {
13
+ default: "",
14
+ plural: true,
15
+ type: "username",
16
+ },
17
+ amount: {
18
+ default: 10,
19
+ type: "number",
20
+ },
21
+ token: {
22
+ default: "usdc",
23
+ type: "string",
24
+ values: ["eth", "dai", "usdc", "degen"],
25
+ },
26
+ },
27
+ },
28
+ ];
29
+
30
+ export async function handleTipping(context: XMTPContext) {
31
+ const {
32
+ message: {
33
+ content: {
34
+ skill,
35
+ params: { amount, username },
36
+ },
37
+ sender,
38
+ },
39
+ } = context;
40
+ let receivers: AbstractedMember[] = [];
41
+
42
+ receivers = await Promise.all(
43
+ username.map((username: string) => getUserInfo(username)),
44
+ );
45
+
46
+ if (!sender || receivers.length === 0 || amount === 0) {
47
+ context.reply("Sender or receiver or amount not found.");
48
+ }
49
+ const receiverAddresses = receivers.map((receiver) => receiver.address);
50
+
51
+ context.sendTo(
52
+ `You received ${amount} tokens from ${sender.address}.`,
53
+ receiverAddresses,
54
+ );
55
+
56
+ // Notify sender of the transaction details
57
+ context.sendTo(
58
+ `You sent ${amount * receiverAddresses.length} tokens in total.`,
59
+ [sender.address],
60
+ );
61
+ }
@@ -1,25 +1,33 @@
1
- import { run, HandlerContext } from "@xmtp/message-kit";
2
- import { textGeneration, processMultilineResponse } from "@xmtp/message-kit";
3
- import { agent_prompt } from "./prompt.js";
1
+ import { run, agentReply, replaceVariables } from "@xmtp/message-kit";
2
+ import { registerSkill as tippingSkill } from "./handlers/tipping.js";
3
+ import { registerSkill as paymentSkill } from "./handlers/payment.js";
4
+ import { registerSkill as gameSkill } from "./handlers/game.js";
5
+ import { registerSkill as helperSkill } from "./handlers/helpers.js";
6
+ import { systemPrompt } from "./prompt.js";
4
7
 
5
- run(async (context: HandlerContext) => {
6
- const {
7
- message: {
8
- content: { text, params },
9
- sender,
10
- },
11
- } = context;
8
+ export const skills = [
9
+ {
10
+ name: "Group bot",
11
+ tag: "@bot",
12
+ description: "Group agent for tipping payments, games and more.",
13
+ skills: [...tippingSkill, ...paymentSkill, ...gameSkill, ...helperSkill],
14
+ },
15
+ ];
12
16
 
13
- try {
14
- let userPrompt = params?.prompt ?? text;
15
- const { reply } = await textGeneration(
17
+ run(
18
+ async (context) => {
19
+ const {
20
+ message: { sender },
21
+ runConfig,
22
+ } = context;
23
+
24
+ let prompt = await replaceVariables(
25
+ systemPrompt,
16
26
  sender.address,
17
- userPrompt,
18
- await agent_prompt(sender.address),
27
+ runConfig?.skills,
28
+ "@bot",
19
29
  );
20
- await processMultilineResponse(sender.address, reply, context);
21
- } catch (error) {
22
- console.error("Error during OpenAI call:", error);
23
- await context.send("An error occurred while processing your request.");
24
- }
25
- });
30
+ await agentReply(context, prompt);
31
+ },
32
+ { skills },
33
+ );
@@ -1,28 +1,33 @@
1
- import { skills } from "./skills.js";
2
- import { defaultPromptTemplate } from "@xmtp/message-kit";
1
+ export const systemPrompt = `
2
+ {persona}
3
3
 
4
- export async function agent_prompt(senderAddress: string) {
5
- let fineTunedPrompt = `
6
- ## Example response
4
+ {rules}
7
5
 
8
- 1. If user wants to play a game, use the skill 'game' and specify the game type.
9
- Hey! Sure let's do that.\n/game wordle
6
+ {user_context}
7
+
8
+ {skills}
9
+
10
+ ## Response Scenarios
11
+
12
+ 1. If the user wants to play a game suggest direcly a game like wordle:
13
+ Let's play wordle!
14
+ /game wordle
10
15
 
11
16
  2. When user wants to pay a specific token:
12
- I'll help you pay 1 USDC to 0x123...\n/pay 1 [token] 0x123456789...
17
+ I'll help you pay 1 USDC to 0x123...
18
+ /pay 1 [token] 0x123456789...
13
19
  *This will return a url to pay
14
20
 
15
21
  3. If the user wants to pay a eth domain:
16
- I'll help you pay 1 USDC to vitalik.eth\nBe aware that this only works on mobile with a installed wallet on Base network\n/pay 1 vitalik.eth
22
+ I'll help you pay 1 USDC to vitalik.eth
23
+ Be aware that this only works on mobile with a installed wallet on Base network
24
+ /pay 1 vitalik.eth
17
25
  *This will return a url to pay
18
26
 
19
27
  4. If the user wants to pay a username:
20
- I'll help you pay 1 USDC to @fabri\nBe aware that this only works on mobile with a installed wallet on Base network\n/pay 1 @fabri
28
+ I'll help you pay 1 USDC to @fabri
29
+ Be aware that this only works on mobile with a installed wallet on Base network
30
+ /pay 1 @fabri
21
31
  *This will return a url to pay
22
32
 
23
- 5. If the user wants to play a game suggest direcly a game like wordle:
24
- Let's play wordle!\n/game wordle
25
- `;
26
-
27
- return defaultPromptTemplate(fineTunedPrompt, senderAddress, skills, "@bot");
28
- }
33
+ `;
@@ -1,174 +0,0 @@
1
- import { HandlerContext, SkillResponse } from "@xmtp/message-kit";
2
- import { getUserInfo, clearInfoCache, isOnXMTP } from "@xmtp/message-kit";
3
- import { clearMemory } from "@xmtp/message-kit";
4
-
5
- export const frameUrl = "https://ens.steer.fun/";
6
- export const ensUrl = "https://app.ens.domains/";
7
- export const txpayUrl = "https://txpay.vercel.app";
8
-
9
- export async function handleEns(
10
- context: HandlerContext,
11
- ): Promise<SkillResponse | undefined> {
12
- const {
13
- message: {
14
- sender,
15
- content: { skill, params },
16
- },
17
- } = context;
18
-
19
- if (skill == "reset") {
20
- clearMemory();
21
- return { code: 200, message: "Conversation reset." };
22
- } else if (skill == "renew") {
23
- // Destructure and validate parameters for the ens
24
- const { domain } = params;
25
- // Check if the user holds the domain
26
- if (!domain) {
27
- return {
28
- code: 400,
29
- message: "Missing required parameters. Please provide domain.",
30
- };
31
- }
32
-
33
- const data = await getUserInfo(domain);
34
-
35
- if (!data?.address || data?.address !== sender?.address) {
36
- return {
37
- code: 403,
38
- message:
39
- "Looks like this domain is not registered to you. Only the owner can renew it.",
40
- };
41
- }
42
-
43
- // Generate URL for the ens
44
- let url_ens = frameUrl + "frames/manage?name=" + domain;
45
- return { code: 200, message: `${url_ens}` };
46
- } else if (skill == "register") {
47
- // Destructure and validate parameters for the ens
48
- const { domain } = params;
49
-
50
- if (!domain) {
51
- return {
52
- code: 400,
53
- message: "Missing required parameters. Please provide domain.",
54
- };
55
- }
56
- // Generate URL for the ens
57
- let url_ens = ensUrl + domain;
58
- context.send(`${url_ens}`);
59
- return { code: 200, message: `${url_ens}` };
60
- } else if (skill == "info") {
61
- const { domain } = params;
62
-
63
- const data = await getUserInfo(domain);
64
- if (!data?.ensDomain) {
65
- return {
66
- code: 404,
67
- message: "Domain not found.",
68
- };
69
- }
70
-
71
- const formattedData = {
72
- Address: data?.address,
73
- "Avatar URL": data?.ensInfo?.avatar,
74
- Description: data?.ensInfo?.description,
75
- ENS: data?.ensDomain,
76
- "Primary ENS": data?.ensInfo?.ens_primary,
77
- GitHub: data?.ensInfo?.github,
78
- Resolver: data?.ensInfo?.resolverAddress,
79
- Twitter: data?.ensInfo?.twitter,
80
- URL: `${ensUrl}${domain}`,
81
- };
82
-
83
- let message = "Domain information:\n\n";
84
- for (const [key, value] of Object.entries(formattedData)) {
85
- if (value) {
86
- message += `${key}: ${value}\n`;
87
- }
88
- }
89
- message += `\n\nWould you like to tip the domain owner for getting there first 🤣?`;
90
- message = message.trim();
91
- if (await isOnXMTP(context.client, context.v2client, sender?.address)) {
92
- await context.send(
93
- `Ah, this domains is in XMTP, you can message it directly: https://converse.xyz/dm/${domain}`,
94
- );
95
- }
96
- return { code: 200, message };
97
- } else if (skill == "check") {
98
- const { domain } = params;
99
-
100
- if (!domain) {
101
- return {
102
- code: 400,
103
- message: "Please provide a domain name to check.",
104
- };
105
- }
106
-
107
- const data = await getUserInfo(domain);
108
- if (!data?.address) {
109
- let message = `Looks like ${domain} is available! Here you can register it: ${ensUrl}${domain} or would you like to see some cool alternatives?`;
110
- return {
111
- code: 200,
112
- message,
113
- };
114
- } else {
115
- let message = `Looks like ${domain} is already registered!`;
116
- await context.executeSkill("/cool " + domain);
117
- return {
118
- code: 404,
119
- message,
120
- };
121
- }
122
- } else if (skill == "tip") {
123
- const { address } = params;
124
- if (!address) {
125
- return {
126
- code: 400,
127
- message: "Please provide an address to tip.",
128
- };
129
- }
130
- const data = await getUserInfo(address);
131
-
132
- let sendUrl = `${txpayUrl}/?&amount=1&token=USDC&receiver=${address}`;
133
-
134
- return {
135
- code: 200,
136
- message: sendUrl,
137
- };
138
- } else if (skill == "cool") {
139
- const { domain } = params;
140
- //What about these cool alternatives?\
141
- return {
142
- code: 200,
143
- message: `${generateCoolAlternatives(domain)}`,
144
- };
145
- } else {
146
- return { code: 400, message: "Skill not found." };
147
- }
148
- }
149
-
150
- export const generateCoolAlternatives = (domain: string) => {
151
- const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
152
- const alternatives = [];
153
- for (let i = 0; i < 5; i++) {
154
- const randomPosition = Math.random() < 0.5;
155
- const baseDomain = domain.replace(/\.eth$/, ""); // Remove any existing .eth suffix
156
- alternatives.push(
157
- randomPosition
158
- ? `${suffixes[i]}${baseDomain}.eth`
159
- : `${baseDomain}${suffixes[i]}.eth`,
160
- );
161
- }
162
-
163
- const cool_alternativesFormat = alternatives
164
- .map(
165
- (alternative: string, index: number) => `${index + 1}. ${alternative} ✨`,
166
- )
167
- .join("\n");
168
- return cool_alternativesFormat;
169
- };
170
-
171
- export async function clear() {
172
- clearMemory();
173
- clearInfoCache();
174
- }
@@ -1,88 +0,0 @@
1
- import { handleEns } from "./handler.js";
2
- import type { SkillGroup } from "@xmtp/message-kit";
3
-
4
- export const skills: SkillGroup[] = [
5
- {
6
- name: "Ens Domain Bot",
7
- tag: "@ens",
8
- description: "Register ENS domains.",
9
- skills: [
10
- {
11
- skill: "/register [domain]",
12
- handler: handleEns,
13
- description:
14
- "Register a new ENS domain. Returns a URL to complete the registration process.",
15
- examples: ["/register vitalik.eth"],
16
- params: {
17
- domain: {
18
- type: "string",
19
- },
20
- },
21
- },
22
- {
23
- skill: "/info [domain]",
24
- handler: handleEns,
25
- description:
26
- "Get detailed information about an ENS domain including owner, expiry date, and resolver.",
27
- examples: ["/info nick.eth"],
28
- params: {
29
- domain: {
30
- type: "string",
31
- },
32
- },
33
- },
34
- {
35
- skill: "/renew [domain]",
36
- handler: handleEns,
37
- description:
38
- "Extend the registration period of your ENS domain. Returns a URL to complete the renewal.",
39
- examples: ["/renew fabri.base.eth"],
40
- params: {
41
- domain: {
42
- type: "string",
43
- },
44
- },
45
- },
46
- {
47
- skill: "/check [domain]",
48
- handler: handleEns,
49
- examples: ["/check vitalik.eth", "/check fabri.base.eth"],
50
- description: "Check if a domain is available.",
51
- params: {
52
- domain: {
53
- type: "string",
54
- },
55
- },
56
- },
57
- {
58
- skill: "/cool [domain]",
59
- examples: ["/cool vitalik.eth"],
60
- handler: handleEns,
61
- description: "Get cool alternatives for a .eth domain.",
62
- params: {
63
- domain: {
64
- type: "string",
65
- },
66
- },
67
- },
68
- {
69
- skill: "/reset",
70
- examples: ["/reset"],
71
- handler: handleEns,
72
- description: "Reset the conversation.",
73
- params: {},
74
- },
75
- {
76
- skill: "/tip [address]",
77
- description: "Show a URL for tipping a domain owner.",
78
- handler: handleEns,
79
- examples: ["/tip 0x1234567890123456789012345678901234567890"],
80
- params: {
81
- address: {
82
- type: "string",
83
- },
84
- },
85
- },
86
- ],
87
- },
88
- ];
@@ -1,2 +0,0 @@
1
- KEY= # the private key of the wallet
2
- OPEN_AI_API_KEY= # sk-proj-...
@@ -1,90 +0,0 @@
1
- import { run, HandlerContext } from "@xmtp/message-kit";
2
- import { ChatOpenAI } from "@langchain/openai";
3
- import { createOpenAIFunctionsAgent, AgentExecutor } from "langchain/agents";
4
- import { tools } from "./skills.js";
5
- import { systemPrompt } from "./prompt.js";
6
- import { ChatPromptTemplate } from "@langchain/core/prompts";
7
-
8
- // Initialize OpenAI chat model
9
- const model = new ChatOpenAI({
10
- temperature: 0.7,
11
- modelName: "gpt-4o",
12
- openAIApiKey: process.env.OPEN_AI_API_KEY,
13
- });
14
-
15
- // Create the prompt template with required variables
16
- const prompt = ChatPromptTemplate.fromMessages([
17
- ["system", systemPrompt],
18
- ]).partial({
19
- tools: tools.map((tool) => `${tool.name}: ${tool.description}`).join("\n"),
20
- tool_names: tools.map((tool) => tool.name).join(", "),
21
- });
22
-
23
- // Initialize chat history storage
24
- const chatHistory = new Map<string, { role: string; content: string }[]>();
25
-
26
- // Initialize the agent and executor
27
- const initializeAgent = async () => {
28
- const agent = await createOpenAIFunctionsAgent({
29
- llm: model,
30
- tools: tools as any,
31
- prompt: await prompt,
32
- });
33
-
34
- return new AgentExecutor({
35
- agent,
36
- tools: tools as any,
37
- returnIntermediateSteps: false,
38
- verbose: true, // Set to true for checking the agent's thought process
39
- });
40
- };
41
-
42
- // Create executor instance
43
- const executor = await initializeAgent();
44
-
45
- run(
46
- async (context: HandlerContext) => {
47
- const {
48
- message: {
49
- content: { text },
50
- sender,
51
- },
52
- } = context;
53
-
54
- console.log("Received message:", text);
55
-
56
- // Get or initialize chat history for this sender
57
- if (!chatHistory.has(sender.address)) {
58
- chatHistory.set(sender.address, []);
59
- }
60
- const userHistory: any = chatHistory.get(sender.address)!;
61
-
62
- // Add user message to history
63
- userHistory.push({ role: "user", content: context.message.content.text });
64
-
65
- try {
66
- // Execute agent with user's message and chat history
67
- const result = await executor.invoke({
68
- input: text,
69
- chat_history: userHistory,
70
- });
71
-
72
- console.log("Agent response:", result.output);
73
- const output = result.output.replace(/\*/g, "");
74
-
75
- // Add assistant's response to history
76
- userHistory.push({ role: "assistant", content: output });
77
-
78
- await context.send(output);
79
- } catch (error) {
80
- console.error("Error:", error);
81
- // Add error message to history
82
- userHistory.push({
83
- role: "assistant",
84
- content: "An error occurred while processing your request.",
85
- });
86
- await context.send("An error occurred while processing your request.");
87
- }
88
- },
89
- { skills: [] },
90
- );
@@ -1,19 +0,0 @@
1
- export const systemPrompt = `You are a helpful AI assistant that can use various tools to help users.
2
-
3
- You have access to the following tools:
4
- {tools}
5
-
6
- Tool Names: {tool_names}
7
-
8
- Instructions:
9
- 1. DO NOT respond in markdown. ALWAYS respond in plain text.
10
- 2. Use tools when appropriate
11
- 3. Be friendly and helpful
12
- 4. Ask for clarification if you don't understand the user's request
13
- 5. For tipping, always confirm the amount with the user once
14
-
15
- Previous conversation history:
16
- {chat_history}
17
-
18
- User Input: {input}
19
- {agent_scratchpad}`;