create-message-kit 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +12 -3
- package/package.json +2 -2
- package/templates/agent/.env.example +2 -0
- package/{examples/one-to-one → templates/agent}/package.json +2 -4
- package/templates/agent/src/handler/ens.ts +211 -0
- package/templates/agent/src/index.ts +14 -0
- package/{examples/group → templates/agent}/src/lib/openai.ts +42 -4
- package/templates/agent/src/lib/resolver.ts +126 -0
- package/templates/agent/src/prompt.ts +81 -0
- package/templates/agent/src/skills.ts +93 -0
- package/templates/gm/.env.example +1 -0
- package/{examples → templates}/gm/package.json +1 -0
- package/templates/group/.env.example +3 -0
- package/{examples → templates}/group/package.json +1 -0
- package/{examples → templates}/group/src/handler/agent.ts +18 -10
- package/{examples → templates}/group/src/handler/game.ts +2 -2
- package/{examples → templates}/group/src/handler/loyalty.ts +3 -11
- package/{examples → templates}/group/src/handler/splitpayment.ts +18 -11
- package/{examples → templates}/group/src/handler/tipping.ts +10 -6
- package/{examples → templates}/group/src/handler/transaction.ts +11 -40
- package/templates/group/src/index.ts +57 -0
- package/templates/group/src/lib/openai.ts +137 -0
- package/templates/group/src/lib/resolver.ts +126 -0
- package/{examples/group/src/commands.ts → templates/group/src/skills.ts} +24 -26
- package/examples/gm/.env.example +0 -1
- package/examples/group/.env.example +0 -3
- package/examples/group/src/index.ts +0 -68
- package/examples/one-to-one/.env.example +0 -2
- package/examples/one-to-one/src/index.ts +0 -72
- package/examples/one-to-one/src/lib/cron.ts +0 -34
- package/examples/one-to-one/src/lib/redis.ts +0 -15
- /package/{examples → templates}/gm/src/index.ts +0 -0
- /package/{examples → templates}/group/src/lib/stack.ts +0 -0
package/index.js
CHANGED
@@ -19,7 +19,16 @@ program
|
|
19
19
|
.name("byob")
|
20
20
|
.description("CLI to initialize projects")
|
21
21
|
.action(async () => {
|
22
|
-
|
22
|
+
log.message(`\x1b[38;2;250;105;119m\
|
23
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
24
|
+
|
25
|
+
███╗ ███╗███████╗███████╗███████╗ █████╗ ██████╗ ███████╗██╗ ██╗██╗████████╗
|
26
|
+
████╗ ████║██╔════╝██╔════╝██╔════╝██╔══██╗██╔════╝ ██╔════╝██║ ██╔╝██║╚══██╔══╝
|
27
|
+
██╔████╔██║█████╗ ███████╗███████╗███████║██║ ███╗█████╗ █████╔╝ ██║ ██║
|
28
|
+
██║╚██╔╝██║██╔══╝ ╚════██║╚════██║██╔══██║██║ ██║██╔══╝ ██╔═██╗ ██║ ██║
|
29
|
+
██║ ╚═╝ ██║███████╗███████║███████║██║ ██║╚██████╔╝███████╗██║ ██╗██║ ██║
|
30
|
+
╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
|
31
|
+
Powered by XMTP \x1b[0m`);
|
23
32
|
|
24
33
|
const { templateType, displayName, destDir } = await gatherProjectInfo();
|
25
34
|
|
@@ -57,7 +66,7 @@ program.parse(process.argv);
|
|
57
66
|
async function gatherProjectInfo() {
|
58
67
|
const templateOptions = [
|
59
68
|
{ value: "gm", label: "GM" },
|
60
|
-
{ value: "
|
69
|
+
{ value: "agent", label: "Agent" },
|
61
70
|
{ value: "group", label: "Group" },
|
62
71
|
];
|
63
72
|
|
@@ -70,7 +79,7 @@ async function gatherProjectInfo() {
|
|
70
79
|
process.exit(0);
|
71
80
|
}
|
72
81
|
|
73
|
-
const templateDir = resolve(__dirname, `./
|
82
|
+
const templateDir = resolve(__dirname, `./templates/${templateType}`);
|
74
83
|
|
75
84
|
// Ensure the template directory exists
|
76
85
|
if (!fs.existsSync(templateDir)) {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "create-message-kit",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.17",
|
4
4
|
"license": "MIT",
|
5
5
|
"type": "module",
|
6
6
|
"main": "index.js",
|
@@ -8,7 +8,7 @@
|
|
8
8
|
"bin": "index.js",
|
9
9
|
"files": [
|
10
10
|
"index.js",
|
11
|
-
"
|
11
|
+
"templates/**/*"
|
12
12
|
],
|
13
13
|
"scripts": {
|
14
14
|
"clean": "rm -rf .turbo && rm -rf node_modules",
|
@@ -1,5 +1,5 @@
|
|
1
1
|
{
|
2
|
-
"name": "
|
2
|
+
"name": "agent",
|
3
3
|
"private": true,
|
4
4
|
"type": "module",
|
5
5
|
"scripts": {
|
@@ -8,13 +8,11 @@
|
|
8
8
|
"start": "node dist/index.js"
|
9
9
|
},
|
10
10
|
"dependencies": {
|
11
|
-
"@redis/client": "^1.5.16",
|
12
11
|
"@xmtp/message-kit": "workspace:*",
|
13
|
-
"
|
12
|
+
"openai": "^4.65.0"
|
14
13
|
},
|
15
14
|
"devDependencies": {
|
16
15
|
"@types/node": "^20.14.2",
|
17
|
-
"@types/node-cron": "^3.0.11",
|
18
16
|
"nodemon": "^3.1.3",
|
19
17
|
"typescript": "^5.4.5"
|
20
18
|
},
|
@@ -0,0 +1,211 @@
|
|
1
|
+
import { HandlerContext } from "@xmtp/message-kit";
|
2
|
+
import { getUserInfo, clearInfoCache, isOnXMTP } from "../lib/resolver.js";
|
3
|
+
import { textGeneration } from "../lib/openai.js";
|
4
|
+
import { processResponseWithSkill } from "../lib/openai.js";
|
5
|
+
import { isAddress } from "viem";
|
6
|
+
import { ens_agent_prompt } from "../prompt.js";
|
7
|
+
import { frameUrl, ensUrl, baseTxUrl } from "../index.js";
|
8
|
+
import { clearChatHistories } from "../lib/openai.js";
|
9
|
+
|
10
|
+
export async function handleEns(context: HandlerContext) {
|
11
|
+
const {
|
12
|
+
message: {
|
13
|
+
content: { command, params, sender },
|
14
|
+
},
|
15
|
+
} = context;
|
16
|
+
if (command == "reset") {
|
17
|
+
clear();
|
18
|
+
return { code: 200, message: "Conversation reset." };
|
19
|
+
} else if (command == "renew") {
|
20
|
+
// Destructure and validate parameters for the ens command
|
21
|
+
const { domain } = params;
|
22
|
+
// Check if the user holds the domain
|
23
|
+
if (!domain) {
|
24
|
+
return {
|
25
|
+
code: 400,
|
26
|
+
message: "Missing required parameters. Please provide domain.",
|
27
|
+
};
|
28
|
+
}
|
29
|
+
|
30
|
+
const data = await getUserInfo(domain);
|
31
|
+
|
32
|
+
if (!data || data?.address !== sender?.address) {
|
33
|
+
return {
|
34
|
+
code: 403,
|
35
|
+
message:
|
36
|
+
"Looks like this domain is not registered to you. Only the owner can renew it.",
|
37
|
+
};
|
38
|
+
}
|
39
|
+
|
40
|
+
// Generate URL for the ens
|
41
|
+
let url_ens = frameUrl + "frames/manage?name=" + domain;
|
42
|
+
return { code: 200, message: `${url_ens}` };
|
43
|
+
} else if (command == "register") {
|
44
|
+
// Destructure and validate parameters for the ens command
|
45
|
+
const { domain } = params;
|
46
|
+
|
47
|
+
if (!domain) {
|
48
|
+
return {
|
49
|
+
code: 400,
|
50
|
+
message: "Missing required parameters. Please provide domain.",
|
51
|
+
};
|
52
|
+
}
|
53
|
+
// Generate URL for the ens
|
54
|
+
let url_ens = ensUrl + domain;
|
55
|
+
return { code: 200, message: `${url_ens}` };
|
56
|
+
} else if (command == "info") {
|
57
|
+
const { domain } = params;
|
58
|
+
|
59
|
+
const data = await getUserInfo(domain);
|
60
|
+
if (!data) {
|
61
|
+
return {
|
62
|
+
code: 404,
|
63
|
+
message: "Domain not found.",
|
64
|
+
};
|
65
|
+
}
|
66
|
+
|
67
|
+
const formattedData = {
|
68
|
+
Address: data?.address,
|
69
|
+
"Avatar URL": data?.ensInfo?.avatar,
|
70
|
+
Description: data?.ensInfo?.description,
|
71
|
+
ENS: data?.ensDomain,
|
72
|
+
"Primary ENS": data?.ensInfo?.ens_primary,
|
73
|
+
GitHub: data?.ensInfo?.github,
|
74
|
+
Resolver: data?.ensInfo?.resolverAddress,
|
75
|
+
Twitter: data?.ensInfo?.twitter,
|
76
|
+
URL: `${ensUrl}${domain}`,
|
77
|
+
};
|
78
|
+
|
79
|
+
let message = "Domain information:\n\n";
|
80
|
+
for (const [key, value] of Object.entries(formattedData)) {
|
81
|
+
if (value) {
|
82
|
+
message += `${key}: ${value}\n`;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
message += `\n\nWould you like to tip the domain owner for getting there first 🤣?`;
|
86
|
+
message = message.trim();
|
87
|
+
if (
|
88
|
+
await isOnXMTP(
|
89
|
+
context.v2client,
|
90
|
+
data?.ensInfo?.ens,
|
91
|
+
data?.ensInfo?.address,
|
92
|
+
)
|
93
|
+
) {
|
94
|
+
await context.send(
|
95
|
+
`Ah, this domains is in XMTP, you can message it directly: https://converse.xyz/dm/${domain}`,
|
96
|
+
);
|
97
|
+
}
|
98
|
+
return { code: 200, message };
|
99
|
+
} else if (command == "check") {
|
100
|
+
const { domain } = params;
|
101
|
+
|
102
|
+
if (!domain) {
|
103
|
+
return {
|
104
|
+
code: 400,
|
105
|
+
message: "Please provide a domain name to check.",
|
106
|
+
};
|
107
|
+
}
|
108
|
+
|
109
|
+
const data = await getUserInfo(domain);
|
110
|
+
if (!data?.address) {
|
111
|
+
let message = `Looks like ${domain} is available! Do you want to register it? ${ensUrl}${domain} or would you like to see some cool alternatives?`;
|
112
|
+
return {
|
113
|
+
code: 200,
|
114
|
+
message,
|
115
|
+
};
|
116
|
+
} else {
|
117
|
+
let message = `Looks like ${domain} is already registered!`;
|
118
|
+
await context.skill("/cool " + domain);
|
119
|
+
return {
|
120
|
+
code: 404,
|
121
|
+
message,
|
122
|
+
};
|
123
|
+
}
|
124
|
+
} else if (command == "tip") {
|
125
|
+
const { address } = params;
|
126
|
+
if (!address) {
|
127
|
+
return {
|
128
|
+
code: 400,
|
129
|
+
message: "Please provide an address to tip.",
|
130
|
+
};
|
131
|
+
}
|
132
|
+
const data = await getUserInfo(address);
|
133
|
+
let txUrl = `${baseTxUrl}/transaction/?transaction_type=send&buttonName=Tip%20${data?.ensDomain ?? ""}&amount=1&token=USDC&receiver=${
|
134
|
+
isAddress(address) ? address : data?.address
|
135
|
+
}`;
|
136
|
+
console.log(txUrl);
|
137
|
+
return {
|
138
|
+
code: 200,
|
139
|
+
message: txUrl,
|
140
|
+
};
|
141
|
+
} else if (command == "cool") {
|
142
|
+
const { domain } = params;
|
143
|
+
//What about these cool alternatives?\
|
144
|
+
return {
|
145
|
+
code: 200,
|
146
|
+
message: `${generateCoolAlternatives(domain)}`,
|
147
|
+
};
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
export async function ensAgent(context: HandlerContext) {
|
152
|
+
if (!process?.env?.OPEN_AI_API_KEY) {
|
153
|
+
console.warn("No OPEN_AI_API_KEY found in .env");
|
154
|
+
return;
|
155
|
+
}
|
156
|
+
|
157
|
+
const {
|
158
|
+
message: {
|
159
|
+
content: { content, params },
|
160
|
+
sender,
|
161
|
+
},
|
162
|
+
group,
|
163
|
+
} = context;
|
164
|
+
|
165
|
+
try {
|
166
|
+
let userPrompt = params?.prompt ?? content;
|
167
|
+
const userInfo = await getUserInfo(sender.address);
|
168
|
+
if (!userInfo) {
|
169
|
+
console.log("User info not found");
|
170
|
+
return;
|
171
|
+
}
|
172
|
+
const { ensDomain, converseUsername } = userInfo;
|
173
|
+
|
174
|
+
const { reply } = await textGeneration(
|
175
|
+
sender.address,
|
176
|
+
userPrompt,
|
177
|
+
await ens_agent_prompt(sender.address, ensDomain, converseUsername),
|
178
|
+
group !== undefined,
|
179
|
+
);
|
180
|
+
await processResponseWithSkill(sender.address, reply, context);
|
181
|
+
} catch (error) {
|
182
|
+
console.error("Error during OpenAI call:", error);
|
183
|
+
await context.send("An error occurred while processing your request.");
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
export const generateCoolAlternatives = (domain: string) => {
|
188
|
+
const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
|
189
|
+
const alternatives = [];
|
190
|
+
for (let i = 0; i < 5; i++) {
|
191
|
+
const randomPosition = Math.random() < 0.5;
|
192
|
+
const baseDomain = domain.replace(/\.eth$/, ""); // Remove any existing .eth suffix
|
193
|
+
alternatives.push(
|
194
|
+
randomPosition
|
195
|
+
? `${suffixes[i]}${baseDomain}.eth`
|
196
|
+
: `${baseDomain}${suffixes[i]}.eth`,
|
197
|
+
);
|
198
|
+
}
|
199
|
+
|
200
|
+
const cool_alternativesFormat = alternatives
|
201
|
+
.map(
|
202
|
+
(alternative: string, index: number) => `${index + 1}. ${alternative} ✨`,
|
203
|
+
)
|
204
|
+
.join("\n");
|
205
|
+
return cool_alternativesFormat;
|
206
|
+
};
|
207
|
+
|
208
|
+
export async function clear() {
|
209
|
+
clearChatHistories();
|
210
|
+
clearInfoCache();
|
211
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { run, HandlerContext } from "@xmtp/message-kit";
|
2
|
+
import { ensAgent } from "./handler/ens.js";
|
3
|
+
|
4
|
+
export const frameUrl = "https://ens.steer.fun/";
|
5
|
+
export const ensUrl = "https://app.ens.domains/";
|
6
|
+
export const baseTxUrl = "https://base-tx-frame.vercel.app";
|
7
|
+
|
8
|
+
run(async (context: HandlerContext) => {
|
9
|
+
const { group, message } = context;
|
10
|
+
/*All the commands are handled through the commands file*/
|
11
|
+
/* If its just text, it will be handled by the ensAgent*/
|
12
|
+
/* If its a group message, it will be handled by the groupAgent*/
|
13
|
+
await ensAgent(context);
|
14
|
+
});
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import dotenv from "dotenv";
|
2
|
-
import { response } from "express";
|
3
2
|
dotenv.config();
|
4
3
|
|
5
4
|
import OpenAI from "openai";
|
@@ -7,12 +6,17 @@ const openai = new OpenAI({
|
|
7
6
|
apiKey: process.env.OPEN_AI_API_KEY,
|
8
7
|
});
|
9
8
|
|
9
|
+
export type ChatHistoryEntry = { role: string; content: string };
|
10
|
+
export type ChatHistories = Record<string, ChatHistoryEntry[]>;
|
11
|
+
|
12
|
+
let chatHistories: ChatHistories = {};
|
10
13
|
export async function textGeneration(
|
14
|
+
address: string,
|
11
15
|
userPrompt: string,
|
12
16
|
systemPrompt: string,
|
13
|
-
|
17
|
+
isGroup: boolean = false,
|
14
18
|
) {
|
15
|
-
let messages =
|
19
|
+
let messages = chatHistories[address] || [];
|
16
20
|
if (messages.length === 0) {
|
17
21
|
messages.push({
|
18
22
|
role: "system",
|
@@ -34,7 +38,7 @@ export async function textGeneration(
|
|
34
38
|
content: reply || "No response from OpenAI.",
|
35
39
|
});
|
36
40
|
const cleanedReply = responseParser(reply as string);
|
37
|
-
|
41
|
+
if (!isGroup) chatHistories[address] = messages;
|
38
42
|
return { reply: cleanedReply, history: messages };
|
39
43
|
} catch (error) {
|
40
44
|
console.error("Failed to fetch from OpenAI:", error);
|
@@ -79,6 +83,35 @@ export async function vision(imageData: Uint8Array, systemPrompt: string) {
|
|
79
83
|
}
|
80
84
|
}
|
81
85
|
|
86
|
+
export async function processResponseWithSkill(
|
87
|
+
address: string,
|
88
|
+
reply: string,
|
89
|
+
context: any,
|
90
|
+
) {
|
91
|
+
let messages = reply
|
92
|
+
.split("\n")
|
93
|
+
.map((message: string) => responseParser(message))
|
94
|
+
.filter((message): message is string => message.length > 0);
|
95
|
+
|
96
|
+
console.log(messages);
|
97
|
+
for (const message of messages) {
|
98
|
+
if (message.startsWith("/")) {
|
99
|
+
const response = await context.skill(message);
|
100
|
+
if (response && response.message) {
|
101
|
+
let msg = responseParser(response.message);
|
102
|
+
|
103
|
+
chatHistories[address].push({
|
104
|
+
role: "system",
|
105
|
+
content: msg,
|
106
|
+
});
|
107
|
+
|
108
|
+
await context.send(response.message);
|
109
|
+
}
|
110
|
+
} else {
|
111
|
+
await context.send(message);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
82
115
|
export function responseParser(message: string) {
|
83
116
|
let trimmedMessage = message;
|
84
117
|
// Remove bold and underline markdown
|
@@ -95,5 +128,10 @@ export function responseParser(message: string) {
|
|
95
128
|
trimmedMessage = trimmedMessage?.replace(/^\s+|\s+$/g, "");
|
96
129
|
// Remove any remaining leading or trailing whitespace
|
97
130
|
trimmedMessage = trimmedMessage.trim();
|
131
|
+
|
98
132
|
return trimmedMessage;
|
99
133
|
}
|
134
|
+
|
135
|
+
export const clearChatHistories = () => {
|
136
|
+
chatHistories = {};
|
137
|
+
};
|
@@ -0,0 +1,126 @@
|
|
1
|
+
import { Client } from "@xmtp/xmtp-js";
|
2
|
+
import { isAddress } from "viem";
|
3
|
+
|
4
|
+
export const converseEndpointURL =
|
5
|
+
"https://converse-website-git-endpoit-ephemerahq.vercel.app";
|
6
|
+
//export const converseEndpointURL = "http://localhost:3000";
|
7
|
+
|
8
|
+
export type InfoCache = Map<string, UserInfo>;
|
9
|
+
export type ConverseProfile = {
|
10
|
+
address: string | null;
|
11
|
+
onXmtp: boolean;
|
12
|
+
avatar: string | null;
|
13
|
+
formattedName: string | null;
|
14
|
+
name: string | null;
|
15
|
+
};
|
16
|
+
export type UserInfo = {
|
17
|
+
ensDomain?: string | undefined;
|
18
|
+
address?: string | undefined;
|
19
|
+
converseUsername?: string | undefined;
|
20
|
+
ensInfo?: EnsData | undefined;
|
21
|
+
avatar?: string | undefined;
|
22
|
+
};
|
23
|
+
export interface EnsData {
|
24
|
+
address?: string;
|
25
|
+
avatar?: string;
|
26
|
+
avatar_small?: string;
|
27
|
+
converse?: string;
|
28
|
+
avatar_url?: string;
|
29
|
+
contentHash?: string;
|
30
|
+
description?: string;
|
31
|
+
ens?: string;
|
32
|
+
ens_primary?: string;
|
33
|
+
github?: string;
|
34
|
+
resolverAddress?: string;
|
35
|
+
twitter?: string;
|
36
|
+
url?: string;
|
37
|
+
wallets?: {
|
38
|
+
eth?: string;
|
39
|
+
};
|
40
|
+
}
|
41
|
+
|
42
|
+
let infoCache: InfoCache = new Map();
|
43
|
+
|
44
|
+
export const clearInfoCache = () => {
|
45
|
+
infoCache.clear();
|
46
|
+
};
|
47
|
+
export const getUserInfo = async (
|
48
|
+
key: string,
|
49
|
+
clientAddress?: string,
|
50
|
+
): Promise<UserInfo | null> => {
|
51
|
+
let data: UserInfo = infoCache.get(key) || {
|
52
|
+
ensDomain: undefined,
|
53
|
+
address: undefined,
|
54
|
+
converseUsername: undefined,
|
55
|
+
ensInfo: undefined,
|
56
|
+
};
|
57
|
+
//console.log("Getting user info", key, clientAddress);
|
58
|
+
if (isAddress(clientAddress || "")) {
|
59
|
+
data.address = clientAddress;
|
60
|
+
} else if (isAddress(key || "")) {
|
61
|
+
data.address = key;
|
62
|
+
} else if (key.includes(".eth")) {
|
63
|
+
data.ensDomain = key;
|
64
|
+
} else if (key == "@user" || key == "@me" || key == "@bot") {
|
65
|
+
data.address = clientAddress;
|
66
|
+
data.ensDomain = key.replace("@", "") + ".eth";
|
67
|
+
data.converseUsername = key.replace("@", "");
|
68
|
+
} else if (key == "@alix") {
|
69
|
+
data.address = "0x3a044b218BaE80E5b9E16609443A192129A67BeA";
|
70
|
+
data.converseUsername = "alix";
|
71
|
+
} else if (key == "@bo") {
|
72
|
+
data.address = "0xbc3246461ab5e1682baE48fa95172CDf0689201a";
|
73
|
+
data.converseUsername = "bo";
|
74
|
+
} else {
|
75
|
+
data.converseUsername = key;
|
76
|
+
}
|
77
|
+
|
78
|
+
let keyToUse = data.address || data.ensDomain || data.converseUsername;
|
79
|
+
let cacheData = keyToUse && infoCache.get(keyToUse);
|
80
|
+
if (cacheData) {
|
81
|
+
//console.log("Getting user info", keyToUse, cacheData);
|
82
|
+
return cacheData;
|
83
|
+
} else {
|
84
|
+
//console.log("Getting user info", keyToUse, data);
|
85
|
+
}
|
86
|
+
|
87
|
+
if (keyToUse?.includes(".eth")) {
|
88
|
+
const response = await fetch(`https://ensdata.net/${keyToUse}`);
|
89
|
+
const ensData: EnsData = (await response.json()) as EnsData;
|
90
|
+
//console.log("Ens data", ensData);
|
91
|
+
if (ensData) {
|
92
|
+
data.ensInfo = ensData;
|
93
|
+
data.ensDomain = ensData?.ens;
|
94
|
+
data.address = ensData?.address;
|
95
|
+
}
|
96
|
+
} else if (keyToUse) {
|
97
|
+
keyToUse = keyToUse.replace("@", "");
|
98
|
+
const response = await fetch(`${converseEndpointURL}/profile/${keyToUse}`, {
|
99
|
+
method: "POST",
|
100
|
+
headers: {
|
101
|
+
"Content-Type": "application/json",
|
102
|
+
Accept: "application/json",
|
103
|
+
},
|
104
|
+
body: JSON.stringify({
|
105
|
+
peer: keyToUse,
|
106
|
+
}),
|
107
|
+
});
|
108
|
+
const converseData = (await response.json()) as ConverseProfile;
|
109
|
+
if (process.env.MSG_LOG)
|
110
|
+
console.log("Converse data", keyToUse, converseData);
|
111
|
+
data.converseUsername =
|
112
|
+
converseData?.formattedName || converseData?.name || undefined;
|
113
|
+
data.address = converseData?.address || undefined;
|
114
|
+
data.avatar = converseData?.avatar || undefined;
|
115
|
+
}
|
116
|
+
if (data.address) infoCache.set(data.address, data);
|
117
|
+
return data;
|
118
|
+
};
|
119
|
+
export const isOnXMTP = async (
|
120
|
+
client: Client,
|
121
|
+
domain: string | undefined,
|
122
|
+
address: string | undefined,
|
123
|
+
) => {
|
124
|
+
if (domain == "fabri.eth") return false;
|
125
|
+
if (address) return (await client.canMessage([address])).length > 0;
|
126
|
+
};
|
@@ -0,0 +1,81 @@
|
|
1
|
+
export async function ens_agent_prompt(
|
2
|
+
address: string,
|
3
|
+
domain?: string,
|
4
|
+
name?: string,
|
5
|
+
converseUsername?: string,
|
6
|
+
txUrl?: string,
|
7
|
+
) {
|
8
|
+
const systemPrompt = `You are a helpful and playful agent that lives inside a web3 messaging app called Converse.
|
9
|
+
- You can respond with multiple messages if needed. Each message should be separated by a newline character.
|
10
|
+
- You can trigger commands by only sending the command in a newline message.
|
11
|
+
- Never announce actions without using a command separated by a newline character.
|
12
|
+
- Only provide answers based on verified information.
|
13
|
+
- Dont answer in markdown format, just answer in plaintext.
|
14
|
+
- Do not make guesses or assumptions
|
15
|
+
- CHECK that you are not missing a command
|
16
|
+
|
17
|
+
User context:
|
18
|
+
- Users address is: ${address}
|
19
|
+
${domain != undefined ? `- User ENS domain is: ${domain}` : ""}
|
20
|
+
${name != undefined ? `- Converse username is: ${name}` : ""}
|
21
|
+
|
22
|
+
## Task
|
23
|
+
- Start by fetch their domain from or Convese username
|
24
|
+
- Call the user by their name or domain, in case they have one
|
25
|
+
- Ask for a name (if they don't have one) so you can suggest domains.
|
26
|
+
|
27
|
+
Commands:
|
28
|
+
- "/info [domain]": To get information about a domain use this command.
|
29
|
+
- "/check [domain]": To check to see if a domain is available use this command.
|
30
|
+
- "/register [domain]": To register a domain use this command. This will return a url pointing to the registration page.
|
31
|
+
- "/renew [domain]": To trigger renewal of a domain use this command. This will return a url with a button to renew the domain.
|
32
|
+
- "/tip [address]": To tip a domain or address use this command. This will return a url with a button to send the tip
|
33
|
+
- "/cool [domain]": To get cool alternatives for a .eth domain use this command.
|
34
|
+
|
35
|
+
Examples:
|
36
|
+
- /check ${domain}
|
37
|
+
- /info nick.eth
|
38
|
+
- /register vitalik.eth
|
39
|
+
- /renew fabri.base.eth
|
40
|
+
- /tip 0xf0EA7663233F99D0c12370671abBb6Cca980a490
|
41
|
+
- /cool vitalik.eth
|
42
|
+
|
43
|
+
## Example response:
|
44
|
+
|
45
|
+
1. Check if the user does not have a ENS domain
|
46
|
+
Hey ${name}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username ${converseUsername}.eth\n/check ${converseUsername}.eth
|
47
|
+
|
48
|
+
2. If the user has a ENS domain
|
49
|
+
Hello ${domain} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${domain}. Give me a moment.\n/check ${domain}
|
50
|
+
|
51
|
+
3. Check if the ENS domain is available
|
52
|
+
Hello! I'll help you get your domain.\n Let's start by checking your ENS domain ${domain}. Give me a moment.\n/check ${domain}
|
53
|
+
|
54
|
+
4. If the ENS domain is available,
|
55
|
+
Looks like ${domain} is available! Would you like to register it?\n/register ${domain}\n or I can suggest some cool alternatives? Le me know!
|
56
|
+
|
57
|
+
5. If the ENS domain is already registered, let me suggest 5 cool alternatives
|
58
|
+
Looks like ${domain} is already registered!\n What about these cool alternatives?\n/cool ${domain}
|
59
|
+
|
60
|
+
6. If the user wants to register a ENS domain, use the command "/register [domain]"
|
61
|
+
Looks like ${domain} is available! Let me help you register it\n/register ${domain}
|
62
|
+
|
63
|
+
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
|
64
|
+
Here is the url to send the tip:\n/tip ${domain}
|
65
|
+
|
66
|
+
8. If the user wants to get information about the ENS domain, use the command "/info [domain]"
|
67
|
+
Hello! I'll help you get info about ${domain}.\n Give me a moment.\n/info ${domain}
|
68
|
+
|
69
|
+
9. If the user wants to renew their domain, use the command "/renew [domain]"
|
70
|
+
Hello! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${domain}. Give me a moment.\n/renew ${domain}
|
71
|
+
|
72
|
+
10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
|
73
|
+
Here are some cool suggestions for your domain.\n/cool ${domain}
|
74
|
+
|
75
|
+
## Most common bug
|
76
|
+
Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
|
77
|
+
But you forgot to add the command at the end of the message.
|
78
|
+
You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
|
79
|
+
`;
|
80
|
+
return systemPrompt;
|
81
|
+
}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import type { SkillGroup } from "@xmtp/message-kit";
|
2
|
+
import { handleEns } from "./handler/ens.js";
|
3
|
+
|
4
|
+
export const skills: SkillGroup[] = [
|
5
|
+
{
|
6
|
+
name: "Ens Domain Bot",
|
7
|
+
description: "Register ENS domains.",
|
8
|
+
skills: [
|
9
|
+
{
|
10
|
+
command: "/register [domain]",
|
11
|
+
triggers: ["/register", "@ensbot"],
|
12
|
+
handler: handleEns,
|
13
|
+
description:
|
14
|
+
"Register a new ENS domain. Returns a URL to complete the registration process.",
|
15
|
+
example: "/register vitalik.eth",
|
16
|
+
params: {
|
17
|
+
domain: {
|
18
|
+
type: "string",
|
19
|
+
},
|
20
|
+
},
|
21
|
+
},
|
22
|
+
{
|
23
|
+
command: "/info [domain]",
|
24
|
+
triggers: ["/info", "@ensbot"],
|
25
|
+
handler: handleEns,
|
26
|
+
description:
|
27
|
+
"Get detailed information about an ENS domain including owner, expiry date, and resolver.",
|
28
|
+
example: "/info nick.eth",
|
29
|
+
params: {
|
30
|
+
domain: {
|
31
|
+
type: "string",
|
32
|
+
},
|
33
|
+
},
|
34
|
+
},
|
35
|
+
{
|
36
|
+
command: "/renew [domain]",
|
37
|
+
triggers: ["/renew", "@ensbot"],
|
38
|
+
handler: handleEns,
|
39
|
+
description:
|
40
|
+
"Extend the registration period of your ENS domain. Returns a URL to complete the renewal.",
|
41
|
+
example: "/renew fabri.base.eth",
|
42
|
+
params: {
|
43
|
+
domain: {
|
44
|
+
type: "string",
|
45
|
+
},
|
46
|
+
},
|
47
|
+
},
|
48
|
+
{
|
49
|
+
command: "/check [domain] [cool_alternatives]",
|
50
|
+
triggers: ["/check"],
|
51
|
+
handler: handleEns,
|
52
|
+
description: "Check if a domain is available.",
|
53
|
+
params: {
|
54
|
+
domain: {
|
55
|
+
type: "string",
|
56
|
+
},
|
57
|
+
cool_alternatives: {
|
58
|
+
type: "quoted",
|
59
|
+
},
|
60
|
+
},
|
61
|
+
},
|
62
|
+
{
|
63
|
+
command: "/cool [domain]",
|
64
|
+
triggers: ["/cool"],
|
65
|
+
handler: handleEns,
|
66
|
+
description: "Get cool alternatives for a .eth domain.",
|
67
|
+
params: {
|
68
|
+
domain: {
|
69
|
+
type: "string",
|
70
|
+
},
|
71
|
+
},
|
72
|
+
},
|
73
|
+
{
|
74
|
+
command: "/reset",
|
75
|
+
triggers: ["/reset"],
|
76
|
+
handler: handleEns,
|
77
|
+
description: "Reset the conversation.",
|
78
|
+
params: {},
|
79
|
+
},
|
80
|
+
{
|
81
|
+
command: "/tip [address]",
|
82
|
+
description: "Show a URL for tipping a domain owner.",
|
83
|
+
triggers: ["/tip"],
|
84
|
+
handler: handleEns,
|
85
|
+
params: {
|
86
|
+
address: {
|
87
|
+
type: "string",
|
88
|
+
},
|
89
|
+
},
|
90
|
+
},
|
91
|
+
],
|
92
|
+
},
|
93
|
+
];
|
@@ -0,0 +1 @@
|
|
1
|
+
KEY= # the private key of the bot wallet
|