create-message-kit 1.1.8 → 1.1.9

Sign up to get free protection for your applications and to get access to all the features.
package/index.js CHANGED
@@ -79,19 +79,25 @@ async function addPackagejson(destDir, name, pkgManager) {
79
79
  dependencies: {
80
80
  "@xmtp/message-kit": "latest",
81
81
  },
82
+ devDependencies: {
83
+ "@types/node": "latest",
84
+ typescript: "latest",
85
+ },
82
86
  engines: {
83
87
  node: ">=20",
84
88
  },
85
89
  };
86
90
 
87
- if (pkgManager.startsWith("yarn")) {
88
- packageTemplate.packageManager = `${pkgManager}`;
91
+ if (pkgManager.includes("yarn")) {
92
+ //packageTemplate.packageManager = `${pkgManager}`;
89
93
  // Add .yarnrc.yml to disable PnP mode
90
- fs.writeFileSync(
91
- resolve(destDir, ".yarnrc.yml"),
92
- "nodeLinker: node-modules\n",
93
- );
94
94
  }
95
+
96
+ fs.writeFileSync(
97
+ resolve(destDir, ".yarnrc.yml"),
98
+ "nodeLinker: node-modules\n",
99
+ );
100
+
95
101
  fs.writeJsonSync(resolve(destDir, "package.json"), packageTemplate, {
96
102
  spaces: 2,
97
103
  });
@@ -99,9 +105,10 @@ async function addPackagejson(destDir, name, pkgManager) {
99
105
 
100
106
  async function gatherProjectInfo() {
101
107
  const templateOptions = [
102
- { value: "gm", label: "GM" },
108
+ { value: "gpt", label: "GPT" },
103
109
  { value: "agent", label: "Agent" },
104
110
  { value: "group", label: "Group" },
111
+ { value: "ens", label: "Ens Agent Pro" },
105
112
  ];
106
113
 
107
114
  const templateType = await select({
@@ -201,11 +208,27 @@ yarn-error.log*
201
208
 
202
209
  fs.writeFileSync(resolve(destDir, ".gitignore"), gitignoreContent.trim());
203
210
  }
204
-
205
211
  async function detectPackageManager() {
206
212
  try {
207
- const pkgManager = await detect();
213
+ // Check if running through bun create
214
+ if (process.env.BUN_CREATE === "true" || process.env._?.includes("bun")) {
215
+ return "bun";
216
+ }
217
+
208
218
  const userAgent = process.env.npm_config_user_agent;
219
+
220
+ // Check if running through npm init
221
+ if (userAgent?.startsWith("npm")) {
222
+ return "npm";
223
+ }
224
+
225
+ // Check for Bun in process.argv
226
+ if (process.argv.some((arg) => arg.includes("bun"))) {
227
+ return "bun";
228
+ }
229
+
230
+ // Fallback to detect for other cases
231
+ const pkgManager = await detect();
209
232
  let version = "";
210
233
 
211
234
  if (userAgent && pkgManager === "yarn") {
@@ -217,11 +240,9 @@ async function detectPackageManager() {
217
240
 
218
241
  return pkgManager + version;
219
242
  } catch (error) {
220
- // Fallback to npm if detection fails
221
243
  return "npm";
222
244
  }
223
245
  }
224
-
225
246
  function kebabcase(str) {
226
247
  return str
227
248
  .replace(/([a-z])([A-Z])/g, "$1-$2")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-message-kit",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -1,11 +1,10 @@
1
1
  import { HandlerContext, SkillResponse } from "@xmtp/message-kit";
2
2
  import { getUserInfo, clearInfoCache, isOnXMTP } from "@xmtp/message-kit";
3
- import { isAddress } from "viem";
4
3
  import { clearMemory } from "@xmtp/message-kit";
5
4
 
6
5
  export const frameUrl = "https://ens.steer.fun/";
7
6
  export const ensUrl = "https://app.ens.domains/";
8
- export const baseTxUrl = "https://base-tx-frame.vercel.app";
7
+ export const txpayUrl = "https://txpay.vercel.app";
9
8
 
10
9
  export async function handleEns(
11
10
  context: HandlerContext,
@@ -56,6 +55,7 @@ export async function handleEns(
56
55
  }
57
56
  // Generate URL for the ens
58
57
  let url_ens = ensUrl + domain;
58
+ context.send(`${url_ens}`);
59
59
  return { code: 200, message: `${url_ens}` };
60
60
  } else if (skill == "info") {
61
61
  const { domain } = params;
@@ -128,13 +128,12 @@ export async function handleEns(
128
128
  };
129
129
  }
130
130
  const data = await getUserInfo(address);
131
- let txUrl = `${baseTxUrl}/transaction/?transaction_type=send&buttonName=Tip%20${data?.ensDomain ?? ""}&amount=1&token=USDC&receiver=${
132
- isAddress(address) ? address : data?.address
133
- }`;
134
- console.log(txUrl);
131
+
132
+ let sendUrl = `${txpayUrl}/?&amount=1&token=USDC&receiver=${address}`;
133
+
135
134
  return {
136
135
  code: 200,
137
- message: txUrl,
136
+ message: sendUrl,
138
137
  };
139
138
  } else if (skill == "cool") {
140
139
  const { domain } = params;
@@ -1,31 +1,55 @@
1
1
  import { run, HandlerContext } from "@xmtp/message-kit";
2
- import { textGeneration, processMultilineResponse } from "@xmtp/message-kit";
3
- import { agent_prompt } from "./prompt.js";
4
- import { getUserInfo } from "@xmtp/message-kit";
2
+ import { agentRun } from "@xmtp/message-kit";
3
+ import { skills } from "./skills.js";
4
+ import { defaultPromptTemplate } from "@xmtp/message-kit";
5
+
6
+ export async function agent_prompt(senderAddress: string) {
7
+ let fineTuning = `
8
+ ## Example responses:
9
+
10
+ 1. Check if the user does not have a ENS domain
11
+ Hey {PREFERRED_NAME}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username with the .eth suffix\n/check {CONVERSE_USERNAME}.eth
12
+
13
+ 2. If the user has a ENS domain
14
+ Hello {PREFERRED_NAME} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/check {ENS_DOMAIN}
15
+
16
+ 3. Check if the ENS domain is available
17
+ Hello! I'll help you get your domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/check {ENS_DOMAIN}
18
+
19
+ 4. If the ENS domain is available,
20
+ Looks like {ENS_DOMAIN} is available! Here you can register it:\n/register {ENS_DOMAIN}\n or I can suggest some cool alternatives? Le me know!
21
+
22
+ 5. If the ENS domain is already registered, let me suggest 5 cool alternatives
23
+ Looks like {ENS_DOMAIN} is already registered!\n What about these cool alternatives?\n/cool {ENS_DOMAIN}
24
+
25
+ 6. If the user wants to register a ENS domain, use the command "/register [domain]"
26
+ Looks like {ENS_DOMAIN} is available! Let me help you register it\n/register {ENS_DOMAIN}
27
+
28
+ 7. If the user wants to directly to tip to the ENS domain owner, use directly the command "/tip [address]", this will return a url but a button to send the tip
29
+ Here is the url to send the tip:\n/tip 0x...
30
+
31
+ 8. If the user wants to get information about the ENS domain, use the command "/info [domain]"
32
+ Hello! I'll help you get info about {ENS_DOMAIN}.\n Give me a moment.\n/info {ENS_DOMAIN}
33
+
34
+ 9. If the user wants to renew their domain, use the command "/renew [domain]"
35
+ Hello! I'll help you get your ENS domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/renew {ENS_DOMAIN}
36
+
37
+ 10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
38
+ Here are some cool suggestions for your domain.\n/cool {ENS_DOMAIN}
39
+
40
+ ## Most common bugs
41
+
42
+ 1. Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
43
+ But you forgot to add the command at the end of the message.
44
+ You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
45
+ `;
46
+
47
+ return defaultPromptTemplate(fineTuning, senderAddress, skills, "@ens");
48
+ }
5
49
 
6
50
  run(async (context: HandlerContext) => {
7
- const {
8
- message: {
9
- content: { text, params },
10
- sender,
11
- },
12
- } = context;
13
-
14
- try {
15
- let userPrompt = params?.prompt ?? text;
16
- const userInfo = await getUserInfo(sender.address);
17
- if (!userInfo) {
18
- console.log("User info not found");
19
- return;
20
- }
21
- const { reply } = await textGeneration(
22
- sender.address,
23
- userPrompt,
24
- await agent_prompt(userInfo),
25
- );
26
- await processMultilineResponse(sender.address, reply, context);
27
- } catch (error) {
28
- console.error("Error during OpenAI call:", error);
29
- await context.send("An error occurred while processing your request.");
30
- }
51
+ agentRun(context, async (address: string) => {
52
+ const result = (await agent_prompt(address)) ?? "No response available";
53
+ return result;
54
+ });
31
55
  });
@@ -1,4 +1,4 @@
1
- import { handleEns } from "./handler/ens.js";
1
+ import { handleEns } from "./handler.js";
2
2
  import type { SkillGroup } from "@xmtp/message-kit";
3
3
 
4
4
  export const skills: SkillGroup[] = [
@@ -9,7 +9,6 @@ export const skills: SkillGroup[] = [
9
9
  skills: [
10
10
  {
11
11
  skill: "/register [domain]",
12
- triggers: ["/register"],
13
12
  handler: handleEns,
14
13
  description:
15
14
  "Register a new ENS domain. Returns a URL to complete the registration process.",
@@ -20,21 +19,8 @@ export const skills: SkillGroup[] = [
20
19
  },
21
20
  },
22
21
  },
23
- {
24
- skill: "/exists",
25
- examples: ["/exists"],
26
- handler: handleEns,
27
- triggers: ["/exists"],
28
- description: "Check if an address is onboarded.",
29
- params: {
30
- address: {
31
- type: "address",
32
- },
33
- },
34
- },
35
22
  {
36
23
  skill: "/info [domain]",
37
- triggers: ["/info"],
38
24
  handler: handleEns,
39
25
  description:
40
26
  "Get detailed information about an ENS domain including owner, expiry date, and resolver.",
@@ -47,7 +33,6 @@ export const skills: SkillGroup[] = [
47
33
  },
48
34
  {
49
35
  skill: "/renew [domain]",
50
- triggers: ["/renew"],
51
36
  handler: handleEns,
52
37
  description:
53
38
  "Extend the registration period of your ENS domain. Returns a URL to complete the renewal.",
@@ -60,7 +45,6 @@ export const skills: SkillGroup[] = [
60
45
  },
61
46
  {
62
47
  skill: "/check [domain]",
63
- triggers: ["/check"],
64
48
  handler: handleEns,
65
49
  examples: ["/check vitalik.eth", "/check fabri.base.eth"],
66
50
  description: "Check if a domain is available.",
@@ -72,7 +56,6 @@ export const skills: SkillGroup[] = [
72
56
  },
73
57
  {
74
58
  skill: "/cool [domain]",
75
- triggers: ["/cool"],
76
59
  examples: ["/cool vitalik.eth"],
77
60
  handler: handleEns,
78
61
  description: "Get cool alternatives for a .eth domain.",
@@ -84,7 +67,6 @@ export const skills: SkillGroup[] = [
84
67
  },
85
68
  {
86
69
  skill: "/reset",
87
- triggers: ["/reset"],
88
70
  examples: ["/reset"],
89
71
  handler: handleEns,
90
72
  description: "Reset the conversation.",
@@ -93,7 +75,6 @@ export const skills: SkillGroup[] = [
93
75
  {
94
76
  skill: "/tip [address]",
95
77
  description: "Show a URL for tipping a domain owner.",
96
- triggers: ["/tip"],
97
78
  handler: handleEns,
98
79
  examples: ["/tip 0x1234567890123456789012345678901234567890"],
99
80
  params: {
@@ -0,0 +1,2 @@
1
+ KEY= # the private key of the wallet
2
+ OPEN_AI_API_KEY= # sk-proj-...
@@ -0,0 +1,90 @@
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
+ );
@@ -0,0 +1,19 @@
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}`;
@@ -0,0 +1,233 @@
1
+ import { DynamicStructuredTool } from "langchain/tools";
2
+ import { getUserInfo } from "@xmtp/message-kit";
3
+ import { clearMemory } from "@xmtp/message-kit";
4
+ import { isAddress } from "viem";
5
+ import { z } from "zod";
6
+
7
+ const frameUrl = "https://ens.steer.fun/";
8
+ const ensUrl = "https://app.ens.domains/";
9
+ const txpayUrl = "https://txpay.vercel.app";
10
+ const converseUrl = "https://converse.xyz/profile/";
11
+
12
+ // Add interface for Converse API response
13
+ interface ConverseProfile {
14
+ address: string;
15
+ avatar?: string;
16
+ formattedName?: string;
17
+ name?: string;
18
+ onXmtp: boolean;
19
+ }
20
+
21
+ // Add function to check XMTP status
22
+ async function checkXMTPStatus(address: string): Promise<boolean> {
23
+ try {
24
+ const response = await fetch(converseUrl + address, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ Accept: "application/json",
29
+ },
30
+ body: JSON.stringify({ address }),
31
+ });
32
+
33
+ if (!response.ok) {
34
+ console.error(`Failed to check XMTP status: ${response.status}`);
35
+ return false;
36
+ }
37
+
38
+ const data = (await response.json()) as ConverseProfile;
39
+ return data.onXmtp;
40
+ } catch (error) {
41
+ console.error("Error checking XMTP status:", error);
42
+ return false;
43
+ }
44
+ }
45
+
46
+ const generateCoolAlternatives = (domain: string) => {
47
+ const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
48
+ const alternatives = [];
49
+ for (let i = 0; i < 5; i++) {
50
+ const randomPosition = Math.random() < 0.5;
51
+ const baseDomain = domain.replace(/\.eth$/, ""); // Remove any existing .eth suffix
52
+ alternatives.push(
53
+ randomPosition
54
+ ? `${suffixes[i]}${baseDomain}.eth`
55
+ : `${baseDomain}${suffixes[i]}.eth`,
56
+ );
57
+ }
58
+
59
+ return alternatives
60
+ .map(
61
+ (alternative: string, index: number) => `${index + 1}. ${alternative} ✨`,
62
+ )
63
+ .join("\n");
64
+ };
65
+
66
+ // Export tools array with all tools
67
+ export const tools = [
68
+ new DynamicStructuredTool({
69
+ name: "reset_ens_conversation",
70
+ description: "Reset the ENS conversation and clear memory",
71
+ schema: z.object({}),
72
+ func: async () => {
73
+ clearMemory();
74
+ return "Conversation reset successfully.";
75
+ },
76
+ }),
77
+
78
+ new DynamicStructuredTool({
79
+ name: "renew_ens_domain",
80
+ description:
81
+ "Generate renewal URL for an ENS domain. Only works if sender owns the domain",
82
+ schema: z.object({
83
+ domain: z.string().describe("The ENS domain to renew"),
84
+ }),
85
+ func: async ({ domain }) => {
86
+ const data = await getUserInfo(domain);
87
+ if (!data?.address) {
88
+ return "Domain not found or not registered.";
89
+ }
90
+ return `${frameUrl}frames/manage?name=${domain}`;
91
+ },
92
+ }),
93
+
94
+ new DynamicStructuredTool({
95
+ name: "register_ens_domain",
96
+ description: "Get URL to register a new ENS domain",
97
+ schema: z.object({
98
+ domain: z.string().describe("The ENS domain to register"),
99
+ }),
100
+ func: async ({ domain }) => {
101
+ if (!domain) return "Please provide a domain name";
102
+ return `${ensUrl}${domain}`;
103
+ },
104
+ }),
105
+
106
+ new DynamicStructuredTool({
107
+ name: "get_ens_info",
108
+ description:
109
+ "Get detailed information about an ENS domain including owner, avatar, description, etc",
110
+ schema: z.object({
111
+ domain: z.string().describe("The ENS domain to get information about"),
112
+ }),
113
+ func: async ({ domain }) => {
114
+ const data = await getUserInfo(domain);
115
+ if (!data?.ensDomain) {
116
+ return "Domain not found.";
117
+ }
118
+
119
+ const formattedData = {
120
+ Address: data?.address,
121
+ "Avatar URL": data?.ensInfo?.avatar,
122
+ Description: data?.ensInfo?.description,
123
+ ENS: data?.ensDomain,
124
+ "Primary ENS": data?.ensInfo?.ens_primary,
125
+ GitHub: data?.ensInfo?.github,
126
+ Resolver: data?.ensInfo?.resolverAddress,
127
+ Twitter: data?.ensInfo?.twitter,
128
+ URL: `${ensUrl}${domain}`,
129
+ };
130
+
131
+ let message = "Domain information:\n\n";
132
+ for (const [key, value] of Object.entries(formattedData)) {
133
+ if (value) {
134
+ message += `${key}: ${value}\n`;
135
+ }
136
+ }
137
+ message +=
138
+ "\nWould you like to tip the domain owner for getting there first? 🤣";
139
+
140
+ // Check XMTP status if we have an address
141
+ if (data.address) {
142
+ const isOnXMTP = await checkXMTPStatus(data.address);
143
+ if (isOnXMTP) {
144
+ message += `\n\nAh, this domain is on XMTP! You can message it directly: https://converse.xyz/dm/${domain}`;
145
+ }
146
+ }
147
+
148
+ return message;
149
+ },
150
+ }),
151
+
152
+ new DynamicStructuredTool({
153
+ name: "check_ens_availability",
154
+ description: "Check if an ENS domain is available for registration",
155
+ schema: z.object({
156
+ domain: z
157
+ .string()
158
+ .transform((str) => str.replace(/^["'](.+)["']$/, "$1")) // Remove quotes if present
159
+ .transform((str) => str.toLowerCase())
160
+ .describe("The ENS domain to check availability for"),
161
+ }),
162
+ func: async ({ domain }) => {
163
+ if (!domain) return "Please provide a domain name to check.";
164
+
165
+ if (domain.includes(".") && !domain.endsWith(".eth")) {
166
+ return "Invalid ENS domain. Only .eth domains are supported.";
167
+ }
168
+
169
+ if (!domain.includes(".")) {
170
+ domain = `${domain}.eth`;
171
+ }
172
+
173
+ const data = await getUserInfo(domain);
174
+ if (!data?.address) {
175
+ return `Looks like ${domain} is available! Here you can register it: ${ensUrl}${domain} or would you like to see some cool alternatives?`;
176
+ } else {
177
+ const alternatives = generateCoolAlternatives(domain);
178
+ return `Looks like ${domain} is already registered!\n\nHere are some cool alternatives:\n${alternatives}`;
179
+ }
180
+ },
181
+ }),
182
+
183
+ new DynamicStructuredTool({
184
+ name: "get_ens_alternatives",
185
+ description: "Generate cool alternative names for an ENS domain",
186
+ schema: z.object({
187
+ domain: z
188
+ .string()
189
+ .describe("The ENS domain to generate alternatives for"),
190
+ }),
191
+ func: async ({ domain }) => {
192
+ if (!domain) return "Please provide a domain name.";
193
+ return `What about these cool alternatives?\n\n${generateCoolAlternatives(domain)}`;
194
+ },
195
+ }),
196
+
197
+ new DynamicStructuredTool({
198
+ name: "get_ens_tip_url",
199
+ description:
200
+ "Generate a URL to tip an ENS domain owner in USDC. Works with both ENS domains and Ethereum addresses.",
201
+ schema: z.object({
202
+ addressOrDomain: z
203
+ .string()
204
+ .describe("The ENS domain or Ethereum address to tip"),
205
+ amount: z
206
+ .number()
207
+ .optional()
208
+ .default(1)
209
+ .describe("The amount of USDC to tip"),
210
+ }),
211
+ func: async ({ addressOrDomain, amount }) => {
212
+ if (!addressOrDomain) {
213
+ return "Please provide an address or ENS domain to tip.";
214
+ }
215
+
216
+ let address: string | undefined;
217
+
218
+ if (isAddress(addressOrDomain)) {
219
+ address = addressOrDomain;
220
+ } else {
221
+ const data = await getUserInfo(addressOrDomain);
222
+ address = data?.address;
223
+ }
224
+
225
+ if (!address) {
226
+ return "Could not resolve address for tipping. Please provide a valid ENS domain or Ethereum address.";
227
+ }
228
+
229
+ let sendUrl = `${txpayUrl}/?&amount=${amount}&token=USDC&receiver=${address}`;
230
+ return sendUrl;
231
+ },
232
+ }),
233
+ ];
@@ -0,0 +1,2 @@
1
+ KEY= # the private key of the agent wallet
2
+ OPEN_AI_API_KEY= # the API key for OpenAI
@@ -0,0 +1,38 @@
1
+ import {
2
+ run,
3
+ HandlerContext,
4
+ textGeneration,
5
+ processMultilineResponse,
6
+ } from "@xmtp/message-kit";
7
+ import { agent_prompt } from "./prompt.js";
8
+
9
+ if (!process.env.OPEN_AI_API_KEY) {
10
+ console.error("OPEN_AI_API_KEY is not set");
11
+ }
12
+
13
+ run(async (context: HandlerContext) => {
14
+ if (!process.env.OPEN_AI_API_KEY) {
15
+ context.send("gm");
16
+ return;
17
+ }
18
+
19
+ const {
20
+ message: {
21
+ content: { text, params },
22
+ sender,
23
+ },
24
+ } = context;
25
+
26
+ try {
27
+ let userPrompt = params?.prompt ?? text;
28
+ const { reply } = await textGeneration(
29
+ sender.address,
30
+ userPrompt,
31
+ await agent_prompt(sender.address),
32
+ );
33
+ await processMultilineResponse(sender.address, reply, context);
34
+ } catch (error) {
35
+ console.error("Error during OpenAI call:", error);
36
+ await context.send("An error occurred while processing your request.");
37
+ }
38
+ });
@@ -0,0 +1,20 @@
1
+ import { defaultPromptTemplate } from "@xmtp/message-kit";
2
+
3
+ export async function agent_prompt(senderAddress: string) {
4
+ let fineTunedPrompt = `
5
+
6
+ ### Context
7
+
8
+ You are a helpful agent that lives inside a web3 messaging group that helps interpret user requests and execute commands.
9
+ The message was sent by @${senderAddress}
10
+
11
+
12
+ Important:
13
+ - If a user asks jokes, make jokes about web3 devs\n
14
+ - If the user asks about performing an action and you can think of a command that would help, answer directly with the command and nothing else.
15
+ - Populate the command with the correct or random values. Always return commands with real values only, using usernames with @ and excluding addresses.\n
16
+ - If the user asks a question or makes a statement that does not clearly map to a command, respond with helpful information or a clarification question.\n
17
+ - If the user is grateful, respond asking for a tip in a playful manner.
18
+ `;
19
+ return defaultPromptTemplate(fineTunedPrompt, senderAddress, [], "@bot");
20
+ }
@@ -6,10 +6,10 @@ export async function handler(context: HandlerContext) {
6
6
  content: { skill, params },
7
7
  },
8
8
  } = context;
9
- const baseUrl = "https://txpay.vercel.app";
9
+ const txpayUrl = "https://txpay.vercel.app";
10
10
 
11
11
  if (skill === "pay") {
12
- const { amount: amountSend, token: tokenSend, username } = params; // [!code hl] // [!code focus]
12
+ const { amount: amountSend, token: tokenSend, username } = params;
13
13
  console.log("username", username);
14
14
  let senderInfo = await getUserInfo(username);
15
15
  if (!amountSend || !tokenSend || !senderInfo) {
@@ -23,7 +23,7 @@ export async function handler(context: HandlerContext) {
23
23
  };
24
24
  }
25
25
 
26
- let sendUrl = `${baseUrl}/?&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
26
+ let sendUrl = `${txpayUrl}/?&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
27
27
  await context.send(`${sendUrl}`);
28
28
  }
29
29
  }
@@ -1,7 +1,6 @@
1
1
  import { run, HandlerContext } from "@xmtp/message-kit";
2
2
  import { textGeneration, processMultilineResponse } from "@xmtp/message-kit";
3
3
  import { agent_prompt } from "./prompt.js";
4
- import { getUserInfo } from "@xmtp/message-kit";
5
4
 
6
5
  run(async (context: HandlerContext) => {
7
6
  const {
@@ -13,15 +12,10 @@ run(async (context: HandlerContext) => {
13
12
 
14
13
  try {
15
14
  let userPrompt = params?.prompt ?? text;
16
- const userInfo = await getUserInfo(sender.address);
17
- if (!userInfo) {
18
- console.log("User info not found");
19
- return;
20
- }
21
15
  const { reply } = await textGeneration(
22
16
  sender.address,
23
17
  userPrompt,
24
- await agent_prompt(userInfo),
18
+ await agent_prompt(sender.address),
25
19
  );
26
20
  await processMultilineResponse(sender.address, reply, context);
27
21
  } catch (error) {
@@ -1,18 +1,7 @@
1
1
  import { skills } from "./skills.js";
2
- import {
3
- UserInfo,
4
- PROMPT_USER_CONTENT,
5
- PROMPT_RULES,
6
- PROMPT_SKILLS_AND_EXAMPLES,
7
- PROMPT_REPLACE_VARIABLES,
8
- } from "@xmtp/message-kit";
9
-
10
- export async function agent_prompt(userInfo: UserInfo) {
11
- let systemPrompt =
12
- PROMPT_RULES +
13
- PROMPT_USER_CONTENT(userInfo) +
14
- PROMPT_SKILLS_AND_EXAMPLES(skills, "@bot");
2
+ import { defaultPromptTemplate } from "@xmtp/message-kit";
15
3
 
4
+ export async function agent_prompt(senderAddress: string) {
16
5
  let fineTunedPrompt = `
17
6
  ## Example response
18
7
 
@@ -20,7 +9,7 @@ export async function agent_prompt(userInfo: UserInfo) {
20
9
  Hey! Sure let's do that.\n/game wordle
21
10
 
22
11
  2. When user wants to pay a specific token:
23
- I'll help you pay 1 USDC to 0x123...\n/pay 1 {TOKE}} 0x123456789...
12
+ I'll help you pay 1 USDC to 0x123...\n/pay 1 [token] 0x123456789...
24
13
  *This will return a url to pay
25
14
 
26
15
  3. If the user wants to pay a eth domain:
@@ -30,16 +19,10 @@ export async function agent_prompt(userInfo: UserInfo) {
30
19
  4. If the user wants to pay a username:
31
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
32
21
  *This will return a url to pay
22
+
23
+ 5. If the user wants to play a game suggest direcly a game like wordle:
24
+ Let's play wordle!\n/game wordle
33
25
  `;
34
26
 
35
- systemPrompt += fineTunedPrompt;
36
- // Replace the variables in the system prompt
37
- systemPrompt = PROMPT_REPLACE_VARIABLES(
38
- systemPrompt,
39
- userInfo?.address ?? "",
40
- userInfo,
41
- "@bot",
42
- );
43
- console.log(systemPrompt);
44
- return systemPrompt;
27
+ return defaultPromptTemplate(fineTunedPrompt, senderAddress, skills, "@bot");
45
28
  }
@@ -12,7 +12,6 @@ export const skills: SkillGroup[] = [
12
12
  skills: [
13
13
  {
14
14
  skill: "/tip [usernames] [amount] [token]",
15
- triggers: ["/tip"],
16
15
  examples: ["/tip @vitalik 10 usdc"],
17
16
  description: "Tip users in a specified token.",
18
17
  handler: tipping,
@@ -26,12 +25,16 @@ export const skills: SkillGroup[] = [
26
25
  default: 10,
27
26
  type: "number",
28
27
  },
28
+ token: {
29
+ default: "usdc",
30
+ type: "string",
31
+ values: ["eth", "dai", "usdc", "degen"],
32
+ },
29
33
  },
30
34
  },
31
35
  {
32
36
  skill: "/pay [amount] [token] [username]",
33
- triggers: ["/pay"],
34
- examples: ["/pay 10 vitalik.eth"],
37
+ examples: ["/pay 10 usdc vitalik.eth", "/pay 1 @alix"],
35
38
  description:
36
39
  "Send a specified amount of a cryptocurrency to a destination address.",
37
40
  handler: payment,
@@ -53,7 +56,6 @@ export const skills: SkillGroup[] = [
53
56
  },
54
57
  {
55
58
  skill: "/game [game]",
56
- triggers: ["/game", "🔎", "🔍"],
57
59
  handler: games,
58
60
  description: "Play a game.",
59
61
  examples: ["/game wordle", "/game slot", "/game help"],
@@ -67,7 +69,6 @@ export const skills: SkillGroup[] = [
67
69
  },
68
70
  {
69
71
  skill: "/help",
70
- triggers: ["/help"],
71
72
  examples: ["/help"],
72
73
  handler: help,
73
74
  description: "Get help with the bot.",
@@ -78,7 +79,6 @@ export const skills: SkillGroup[] = [
78
79
  adminOnly: true,
79
80
  examples: ["/id"],
80
81
  handler: help,
81
- triggers: ["/id"],
82
82
  description: "Get the group ID.",
83
83
  params: {},
84
84
  },
@@ -1,69 +0,0 @@
1
- import { skills } from "./skills.js";
2
- import {
3
- UserInfo,
4
- PROMPT_USER_CONTENT,
5
- PROMPT_RULES,
6
- PROMPT_SKILLS_AND_EXAMPLES,
7
- PROMPT_REPLACE_VARIABLES,
8
- } from "@xmtp/message-kit";
9
-
10
- export async function agent_prompt(userInfo: UserInfo) {
11
- let systemPrompt =
12
- PROMPT_RULES +
13
- PROMPT_USER_CONTENT(userInfo) +
14
- PROMPT_SKILLS_AND_EXAMPLES(skills, "@ens");
15
-
16
- let fineTunning = `
17
-
18
- ## Example responses:
19
-
20
- 1. Check if the user does not have a ENS domain
21
- Hey {PREFERRED_NAME}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username with the .eth suffix\n/check {CONVERSE_USERNAME}.eth
22
-
23
- 2. If the user has a ENS domain
24
- Hello {PREFERRED_NAME} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/check {ENS_DOMAIN}
25
-
26
- 3. Check if the ENS domain is available
27
- Hello! I'll help you get your domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/check {ENS_DOMAIN}
28
-
29
- 4. If the ENS domain is available,
30
- Looks like {ENS_DOMAIN} is available! Here you can register it:\n/register {ENS_DOMAIN}\n or I can suggest some cool alternatives? Le me know!
31
-
32
- 5. If the ENS domain is already registered, let me suggest 5 cool alternatives
33
- Looks like {ENS_DOMAIN} is already registered!\n What about these cool alternatives?\n/cool {ENS_DOMAIN}
34
-
35
- 6. If the user wants to register a ENS domain, use the command "/register [domain]"
36
- Looks like {ENS_DOMAIN} is available! Let me help you register it\n/register {ENS_DOMAIN}
37
-
38
- 7. If the user wants to directly to tip to the ENS domain owner, use directly the command "/tip [domain]", this will return a url but a button to send the tip
39
- Here is the url to send the tip:\n/tip {ENS_DOMAIN}
40
-
41
- 8. If the user wants to get information about the ENS domain, use the command "/info [domain]"
42
- Hello! I'll help you get info about {ENS_DOMAIN}.\n Give me a moment.\n/info {ENS_DOMAIN}
43
-
44
- 9. If the user wants to renew their domain, use the command "/renew [domain]"
45
- Hello! I'll help you get your ENS domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/renew {ENS_DOMAIN}
46
-
47
- 10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
48
- Here are some cool suggestions for your domain.\n/cool {ENS_DOMAIN}
49
-
50
- ## Most common bugs
51
-
52
- 1. Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
53
- But you forgot to add the command at the end of the message.
54
- You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
55
- `;
56
-
57
- // Add the fine tuning to the system prompt
58
- systemPrompt += fineTunning;
59
-
60
- // Replace the variables in the system prompt
61
- systemPrompt = PROMPT_REPLACE_VARIABLES(
62
- systemPrompt,
63
- userInfo?.address ?? "",
64
- userInfo,
65
- "@ens",
66
- );
67
- // console.log(systemPrompt);
68
- return systemPrompt;
69
- }
@@ -1 +0,0 @@
1
- KEY= # the private key of the agent wallet
@@ -1,5 +0,0 @@
1
- import { run, HandlerContext } from "@xmtp/message-kit";
2
-
3
- run(async (context: HandlerContext) => {
4
- context.send("gm");
5
- });