Skip to content

Commit 02a4b61

Browse files
jonathanguswtfsayo
andauthored
feat: Add more actions to Abstract Plugin (#2531)
* Start on adding more actions to abstract plugin * format and lint code * update on coderabbit comments --------- Co-authored-by: Sayo <hi@sayo.wtf>
1 parent 63d1ecc commit 02a4b61

15 files changed

+1381
-396
lines changed

packages/plugin-abstract/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@
1919
"dist"
2020
],
2121
"dependencies": {
22-
"@abstract-foundation/agw-client": "^0.1.7",
22+
"@abstract-foundation/agw-client": "1.0.1",
2323
"@elizaos/core": "workspace:*",
2424
"tsup": "^8.3.5",
2525
"viem": "2.22.2"
2626
},
2727
"scripts": {
28-
"build": "tsup --format esm --dts"
28+
"build": "tsup --format esm --dts",
29+
"dev": "tsup --format esm --dts --watch"
2930
},
3031
"peerDependencies": {
3132
"whatwg-url": "7.1.0"
3233
}
33-
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import type { Action } from "@elizaos/core";
2+
import {
3+
type ActionExample,
4+
type Content,
5+
type HandlerCallback,
6+
type IAgentRuntime,
7+
type Memory,
8+
ModelClass,
9+
type State,
10+
elizaLogger,
11+
composeContext,
12+
generateObject,
13+
stringToUuid,
14+
} from "@elizaos/core";
15+
import { validateAbstractConfig } from "../environment";
16+
import { parseEther, type Hash } from "viem";
17+
import { abstractTestnet } from "viem/chains";
18+
import {
19+
type AbstractClient,
20+
createAbstractClient,
21+
} from "@abstract-foundation/agw-client";
22+
import { z } from "zod";
23+
import { useGetAccount, useGetWalletClient } from "../hooks";
24+
import basicToken from "../constants/contracts/basicToken.json";
25+
import { abstractPublicClient } from "../utils/viemHelpers";
26+
27+
const DeploySchema = z.object({
28+
name: z.string(),
29+
symbol: z.string(),
30+
initialSupply: z.string(),
31+
useAGW: z.boolean(),
32+
});
33+
34+
const validatedSchema = z.object({
35+
name: z.string().min(1, "Name is required"),
36+
symbol: z
37+
.string()
38+
.min(1, "Symbol is required")
39+
.max(5, "Symbol must be 5 characters or less"),
40+
initialSupply: z
41+
.string()
42+
.refine((val) => !Number.isNaN(Number(val)) && Number(val) > 0, {
43+
message: "Initial supply must be a positive number",
44+
}),
45+
useAGW: z.boolean(),
46+
});
47+
48+
export interface DeployContent extends Content {
49+
name: string;
50+
symbol: string;
51+
initialSupply: string;
52+
useAGW: boolean;
53+
}
54+
55+
const deployTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
56+
57+
Example response:
58+
\`\`\`json
59+
{
60+
"name": "My Token",
61+
"symbol": "MTK",
62+
"initialSupply": "1000000",
63+
"useAGW": true
64+
}
65+
\`\`\`
66+
67+
User message:
68+
"{{currentMessage}}"
69+
70+
Given the message, extract the following information about the requested token deployment:
71+
- Token name
72+
- Token symbol (usually 3-4 characters)
73+
- Initial supply amount
74+
- Whether to use Abstract Global Wallet aka AGW
75+
76+
If the user did not specify "global wallet", "AGW", "agw", or "abstract global wallet" in their message, set useAGW to false, otherwise set it to true.
77+
78+
Respond with a JSON markdown block containing only the extracted values.`;
79+
80+
export const deployTokenAction: Action = {
81+
name: "DEPLOY_TOKEN",
82+
similes: [
83+
"CREATE_TOKEN",
84+
"DEPLOY_NEW_TOKEN",
85+
"CREATE_NEW_TOKEN",
86+
"LAUNCH_TOKEN",
87+
],
88+
validate: async (runtime: IAgentRuntime) => {
89+
await validateAbstractConfig(runtime);
90+
return true;
91+
},
92+
description: "Deploy a new ERC20 token contract",
93+
handler: async (
94+
runtime: IAgentRuntime,
95+
message: Memory,
96+
state: State,
97+
_options: { [key: string]: unknown },
98+
callback?: HandlerCallback,
99+
): Promise<boolean> => {
100+
elizaLogger.log("Starting Abstract DEPLOY_TOKEN handler...");
101+
102+
if (!state) {
103+
state = (await runtime.composeState(message)) as State;
104+
} else {
105+
state = await runtime.updateRecentMessageState(state);
106+
}
107+
108+
state.currentMessage = `${state.recentMessagesData[1].content.text}`;
109+
const deployContext = composeContext({
110+
state,
111+
template: deployTemplate,
112+
});
113+
114+
const content = (
115+
await generateObject({
116+
runtime,
117+
context: deployContext,
118+
modelClass: ModelClass.SMALL,
119+
schema: DeploySchema,
120+
})
121+
).object as DeployContent;
122+
123+
// Validate deployment content
124+
const result = validatedSchema.safeParse(content);
125+
if (!result.success) {
126+
elizaLogger.error("Invalid content for DEPLOY_TOKEN action.", {
127+
errors: result.error.errors,
128+
});
129+
if (callback) {
130+
callback({
131+
text: "Unable to process token deployment request. Invalid parameters provided.",
132+
content: { error: "Invalid deployment parameters" },
133+
});
134+
}
135+
return false;
136+
}
137+
138+
try {
139+
const account = useGetAccount(runtime);
140+
const supply = parseEther(content.initialSupply);
141+
let hash: Hash;
142+
143+
if (content.useAGW) {
144+
const abstractClient = (await createAbstractClient({
145+
chain: abstractTestnet,
146+
signer: account,
147+
})) as any; // type being exported as never
148+
149+
hash = await abstractClient.deployContract({
150+
abi: basicToken.abi,
151+
bytecode: basicToken.bytecode,
152+
args: [result.data.name, result.data.symbol, supply],
153+
});
154+
} else {
155+
const walletClient = useGetWalletClient();
156+
157+
hash = await walletClient.deployContract({
158+
chain: abstractTestnet,
159+
account,
160+
abi: basicToken.abi,
161+
bytecode: basicToken.bytecode,
162+
args: [result.data.name, result.data.symbol, supply],
163+
kzg: undefined,
164+
});
165+
}
166+
167+
// Wait for transaction receipt
168+
const receipt = await abstractPublicClient.waitForTransactionReceipt({
169+
hash,
170+
});
171+
const contractAddress = receipt.contractAddress;
172+
173+
elizaLogger.success(
174+
`Token deployment completed! Contract address: ${contractAddress}. Transaction hash: ${hash}`,
175+
);
176+
if (callback) {
177+
callback({
178+
text: `Token "${result.data.name}" (${result.data.symbol}) deployed successfully! Contract address: ${contractAddress} and transaction hash: ${hash}`,
179+
content: {
180+
hash,
181+
tokenName: result.data.name,
182+
tokenSymbol: result.data.symbol,
183+
contractAddress,
184+
transactionHash: hash,
185+
},
186+
});
187+
}
188+
189+
const metadata = {
190+
tokenAddress: contractAddress,
191+
name: result.data.name,
192+
symbol: result.data.symbol,
193+
initialSupply: String(result.data.initialSupply),
194+
};
195+
196+
await runtime.messageManager.createMemory({
197+
id: stringToUuid(`${result.data.symbol}-${runtime.agentId}`),
198+
userId: runtime.agentId,
199+
content: {
200+
text: `Token deployed: ${result.data.name}, symbol: ${result.data.symbol} and contract address: ${contractAddress}`,
201+
...metadata,
202+
source: "abstract_token_deployment",
203+
},
204+
agentId: runtime.agentId,
205+
roomId: stringToUuid(`tokens-${runtime.agentId}`),
206+
createdAt: Date.now(),
207+
});
208+
elizaLogger.success("memory saved for token deployment", metadata);
209+
210+
return true;
211+
} catch (error) {
212+
elizaLogger.error("Error during token deployment:", error);
213+
if (callback) {
214+
callback({
215+
text: `Error deploying token: ${error.message}`,
216+
content: { error: error.message },
217+
});
218+
}
219+
return false;
220+
}
221+
},
222+
223+
examples: [
224+
[
225+
{
226+
user: "{{user1}}",
227+
content: {
228+
text: "Deploy a new token called MyToken with symbol MTK and initial supply of 1000000",
229+
},
230+
},
231+
{
232+
user: "{{agent}}",
233+
content: {
234+
text: "I'll deploy your new token now.",
235+
action: "DEPLOY_TOKEN",
236+
},
237+
},
238+
{
239+
user: "{{agent}}",
240+
content: {
241+
text: "Successfully deployed MyToken (MTK) with 1000000 initial supply.\nContract address: 0xdde850f9257365fffffc11324726ebdcf5b90b01c6eec9b3e7ab3e81fde6f14b\nTransaction hash: 0xdde850f9257365fffffc11324726ebdcf5b90b01c6eec9b3e7ab3e81fde6f14b",
242+
},
243+
},
244+
],
245+
[
246+
{
247+
user: "{{user1}}",
248+
content: {
249+
text: "Create a new token using AGW with name TestCoin, symbol TEST, and 5000 supply",
250+
},
251+
},
252+
{
253+
user: "{{agent}}",
254+
content: {
255+
text: "I'll deploy your token using the Abstract Global Wallet.",
256+
action: "DEPLOY_TOKEN",
257+
},
258+
},
259+
{
260+
user: "{{agent}}",
261+
content: {
262+
text: "Successfully deployed TestCoin (TEST) with 5000 initial supply using AGW.\nContract address: 0xdde850f9257365fffffc11324726ebdcf5b90b01c6eec9b3e7ab3e81fde6f14b\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec",
263+
},
264+
},
265+
],
266+
] as ActionExample[][],
267+
};

0 commit comments

Comments
 (0)