Skip to content

Commit 09f0053

Browse files
authored
Merge branch 'develop' into main
2 parents c1b7796 + 3edac48 commit 09f0053

File tree

12 files changed

+408
-23
lines changed

12 files changed

+408
-23
lines changed

.env.example

+5
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ TOGETHER_API_KEY=
191191
# Server Configuration
192192
SERVER_PORT=3000
193193

194+
# Abstract Configuration
195+
ABSTRACT_ADDRESS=
196+
ABSTRACT_PRIVATE_KEY=
197+
ABSTRACT_RPC_URL=https://api.testnet.abs.xyz
198+
194199
# Starknet Configuration
195200
STARKNET_ADDRESS=
196201
STARKNET_PRIVATE_KEY=

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@elizaos/client-slack": "workspace:*",
3131
"@elizaos/core": "workspace:*",
3232
"@elizaos/plugin-0g": "workspace:*",
33+
"@elizaos/plugin-abstract": "workspace:*",
3334
"@elizaos/plugin-aptos": "workspace:*",
3435
"@elizaos/plugin-bootstrap": "workspace:*",
3536
"@elizaos/plugin-intiface": "workspace:*",

agent/src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { suiPlugin } from "@elizaos/plugin-sui";
5555
import { TEEMode, teePlugin } from "@elizaos/plugin-tee";
5656
import { tonPlugin } from "@elizaos/plugin-ton";
5757
import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
58+
import { abstractPlugin } from "@elizaos/plugin-abstract";
5859
import Database from "better-sqlite3";
5960
import fs from "fs";
6061
import path from "path";
@@ -533,6 +534,9 @@ export async function createAgent(
533534
? webhookPlugin
534535
: null,
535536
getSecret(character, "EVM_PROVIDER_URL") ? goatPlugin : null,
537+
getSecret(character, "ABSTRACT_PRIVATE_KEY")
538+
? abstractPlugin
539+
: null,
536540
getSecret(character, "FLOW_ADDRESS") &&
537541
getSecret(character, "FLOW_PRIVATE_KEY")
538542
? flowPlugin

packages/plugin-abstract/package.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "@elizaos/plugin-abstract",
3+
"version": "0.1.7-alpha.1",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@elizaos/core": "workspace:*",
9+
"tsup": "^8.3.5",
10+
"web3": "^4.15.0",
11+
"viem": "2.21.53"
12+
},
13+
"scripts": {
14+
"build": "tsup --format esm --dts"
15+
},
16+
"peerDependencies": {
17+
"whatwg-url": "7.1.0"
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import {
2+
ActionExample,
3+
Content,
4+
HandlerCallback,
5+
IAgentRuntime,
6+
Memory,
7+
ModelClass,
8+
State,
9+
type Action,
10+
elizaLogger,
11+
composeContext,
12+
generateObject,
13+
} from "@elizaos/core";
14+
import { validateAbstractConfig } from "../environment";
15+
16+
import { Address, createWalletClient, erc20Abi, http, parseEther } from "viem";
17+
import { abstractTestnet } from "viem/chains";
18+
import { privateKeyToAccount } from "viem/accounts";
19+
import { eip712WalletActions } from "viem/zksync";
20+
import { z } from "zod";
21+
22+
const TransferSchema = z.object({
23+
tokenAddress: z.string(),
24+
recipient: z.string(),
25+
amount: z.string(),
26+
});
27+
28+
export interface TransferContent extends Content {
29+
tokenAddress: string;
30+
recipient: string;
31+
amount: string | number;
32+
}
33+
34+
export function isTransferContent(
35+
content: TransferContent
36+
): content is TransferContent {
37+
// Validate types
38+
const validTypes =
39+
typeof content.tokenAddress === "string" &&
40+
typeof content.recipient === "string" &&
41+
(typeof content.amount === "string" ||
42+
typeof content.amount === "number");
43+
if (!validTypes) {
44+
return false;
45+
}
46+
47+
// Validate addresses
48+
const validAddresses =
49+
content.tokenAddress.startsWith("0x") &&
50+
content.tokenAddress.length === 42 &&
51+
content.recipient.startsWith("0x") &&
52+
content.recipient.length === 42;
53+
54+
return validAddresses;
55+
}
56+
57+
const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
58+
59+
Here are several frequently used addresses. Use these for the corresponding tokens:
60+
- ETH/eth: 0x000000000000000000000000000000000000800A
61+
- USDC/usdc: 0xe4c7fbb0a626ed208021ccaba6be1566905e2dfc
62+
63+
Example response:
64+
\`\`\`json
65+
{
66+
"tokenAddress": "0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E",
67+
"recipient": "0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
68+
"amount": "1000"
69+
}
70+
\`\`\`
71+
72+
{{recentMessages}}
73+
74+
Given the recent messages, extract the following information about the requested token transfer:
75+
- Token contract address
76+
- Recipient wallet address
77+
- Amount to transfer
78+
79+
Respond with a JSON markdown block containing only the extracted values.`;
80+
81+
const ETH_ADDRESS = "0x000000000000000000000000000000000000800A";
82+
const ERC20_OVERRIDE_INFO = {
83+
"0xe4c7fbb0a626ed208021ccaba6be1566905e2dfc": {
84+
name: "USDC",
85+
decimals: 6,
86+
},
87+
};
88+
89+
export default {
90+
name: "SEND_TOKEN",
91+
similes: [
92+
"TRANSFER_TOKEN_ON_ABSTRACT",
93+
"TRANSFER_TOKENS_ON_ABSTRACT",
94+
"SEND_TOKENS_ON_ABSTRACT",
95+
"SEND_ETH_ON_ABSTRACT",
96+
"PAY_ON_ABSTRACT",
97+
"MOVE_TOKENS_ON_ABSTRACT",
98+
"MOVE_ETH_ON_ABSTRACT",
99+
],
100+
validate: async (runtime: IAgentRuntime, message: Memory) => {
101+
await validateAbstractConfig(runtime);
102+
return true;
103+
},
104+
description: "Transfer tokens from the agent's wallet to another address",
105+
handler: async (
106+
runtime: IAgentRuntime,
107+
message: Memory,
108+
state: State,
109+
_options: { [key: string]: unknown },
110+
callback?: HandlerCallback
111+
): Promise<boolean> => {
112+
elizaLogger.log("Starting Abstract SEND_TOKEN handler...");
113+
114+
// Initialize or update state
115+
if (!state) {
116+
state = (await runtime.composeState(message)) as State;
117+
} else {
118+
state = await runtime.updateRecentMessageState(state);
119+
}
120+
121+
// Compose transfer context
122+
const transferContext = composeContext({
123+
state,
124+
template: transferTemplate,
125+
});
126+
127+
// Generate transfer content
128+
const content = (
129+
await generateObject({
130+
runtime,
131+
context: transferContext,
132+
modelClass: ModelClass.SMALL,
133+
schema: TransferSchema,
134+
})
135+
).object as unknown as TransferContent;
136+
137+
// Validate transfer content
138+
if (!isTransferContent(content)) {
139+
console.error("Invalid content for TRANSFER_TOKEN action.");
140+
if (callback) {
141+
callback({
142+
text: "Unable to process transfer request. Invalid content provided.",
143+
content: { error: "Invalid transfer content" },
144+
});
145+
}
146+
return false;
147+
}
148+
149+
try {
150+
const PRIVATE_KEY = runtime.getSetting("ABSTRACT_PRIVATE_KEY")!;
151+
const account = privateKeyToAccount(`0x${PRIVATE_KEY}`);
152+
153+
const walletClient = createWalletClient({
154+
chain: abstractTestnet,
155+
transport: http(),
156+
}).extend(eip712WalletActions());
157+
158+
let hash;
159+
if (
160+
content.tokenAddress.toLowerCase() !== ETH_ADDRESS.toLowerCase()
161+
) {
162+
// Convert amount to proper token decimals
163+
const tokenInfo =
164+
ERC20_OVERRIDE_INFO[content.tokenAddress.toLowerCase()];
165+
const decimals = tokenInfo?.decimals ?? 18; // Default to 18 decimals if not specified
166+
const tokenAmount =
167+
BigInt(content.amount) * BigInt(10 ** decimals);
168+
169+
// Execute ERC20 transfer
170+
hash = await walletClient.writeContract({
171+
account,
172+
chain: abstractTestnet,
173+
address: content.tokenAddress as Address,
174+
abi: erc20Abi,
175+
functionName: "transfer",
176+
args: [content.recipient as Address, tokenAmount],
177+
});
178+
} else {
179+
hash = await walletClient.sendTransaction({
180+
account: account,
181+
chain: abstractTestnet,
182+
to: content.recipient as Address,
183+
value: parseEther(content.amount.toString()),
184+
kzg: undefined,
185+
});
186+
}
187+
188+
elizaLogger.success(
189+
"Transfer completed successfully! Transaction hash: " + hash
190+
);
191+
if (callback) {
192+
callback({
193+
text:
194+
"Transfer completed successfully! Transaction hash: " +
195+
hash,
196+
content: {},
197+
});
198+
}
199+
200+
return true;
201+
} catch (error) {
202+
elizaLogger.error("Error during token transfer:", error);
203+
if (callback) {
204+
callback({
205+
text: `Error transferring tokens: ${error.message}`,
206+
content: { error: error.message },
207+
});
208+
}
209+
return false;
210+
}
211+
},
212+
213+
examples: [
214+
[
215+
{
216+
user: "{{user1}}",
217+
content: {
218+
text: "Send 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
219+
},
220+
},
221+
{
222+
user: "{{agent}}",
223+
content: {
224+
text: "Sure, I'll send 100 USDC to that address now.",
225+
action: "SEND_TOKEN",
226+
},
227+
},
228+
{
229+
user: "{{agent}}",
230+
content: {
231+
text: "Successfully sent 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec",
232+
},
233+
},
234+
],
235+
[
236+
{
237+
user: "{{user1}}",
238+
content: {
239+
text: "Please send 0.1 ETH to 0xbD8679cf79137042214fA4239b02F4022208EE82",
240+
},
241+
},
242+
{
243+
user: "{{agent}}",
244+
content: {
245+
text: "Of course. Sending 0.1 ETH to that address now.",
246+
action: "SEND_TOKEN",
247+
},
248+
},
249+
{
250+
user: "{{agent}}",
251+
content: {
252+
text: "Successfully sent 0.1 ETH to 0xbD8679cf79137042214fA4239b02F4022208EE82\nTransaction: 0x0b9f23e69ea91ba98926744472717960cc7018d35bc3165bdba6ae41670da0f0",
253+
},
254+
},
255+
],
256+
] as ActionExample[][],
257+
} as Action;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { IAgentRuntime } from "@elizaos/core";
2+
import { z } from "zod";
3+
4+
export const abstractEnvSchema = z.object({
5+
ABSTRACT_ADDRESS: z.string().min(1, "Abstract address is required"),
6+
ABSTRACT_PRIVATE_KEY: z.string().min(1, "Abstract private key is required"),
7+
});
8+
9+
export type AbstractConfig = z.infer<typeof abstractEnvSchema>;
10+
11+
export async function validateAbstractConfig(
12+
runtime: IAgentRuntime
13+
): Promise<AbstractConfig> {
14+
try {
15+
const config = {
16+
ABSTRACT_ADDRESS: runtime.getSetting("ABSTRACT_ADDRESS"),
17+
ABSTRACT_PRIVATE_KEY: runtime.getSetting("ABSTRACT_PRIVATE_KEY"),
18+
};
19+
20+
return abstractEnvSchema.parse(config);
21+
} catch (error) {
22+
if (error instanceof z.ZodError) {
23+
const errorMessages = error.errors
24+
.map((err) => `${err.path.join(".")}: ${err.message}`)
25+
.join("\n");
26+
throw new Error(
27+
`Abstract configuration validation failed:\n${errorMessages}`
28+
);
29+
}
30+
throw error;
31+
}
32+
}

packages/plugin-abstract/src/index.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Plugin } from "@elizaos/core";
2+
3+
import transfer from "./actions/transfer.ts";
4+
5+
export const abstractPlugin: Plugin = {
6+
name: "abstract",
7+
description: "Abstract Plugin for Eliza",
8+
actions: [transfer],
9+
evaluators: [],
10+
providers: [],
11+
};
12+
13+
export default abstractPlugin;
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../core/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "src"
6+
},
7+
"include": [
8+
"src/**/*.ts"
9+
]
10+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig({
4+
entry: ["src/index.ts"],
5+
outDir: "dist",
6+
sourcemap: true,
7+
clean: true,
8+
format: ["esm"], // Ensure you're targeting CommonJS
9+
external: [
10+
"dotenv", // Externalize dotenv to prevent bundling
11+
"fs", // Externalize fs to use Node.js built-in module
12+
"path", // Externalize other built-ins if necessary
13+
"@reflink/reflink",
14+
"@node-llama-cpp",
15+
"https",
16+
"http",
17+
"agentkeepalive",
18+
// Add other modules you want to externalize
19+
],
20+
});

0 commit comments

Comments
 (0)