Skip to content

Commit

Permalink
Merge branch 'develop' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
twilwa authored Dec 25, 2024
2 parents c1b7796 + 3edac48 commit 09f0053
Show file tree
Hide file tree
Showing 12 changed files with 408 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ TOGETHER_API_KEY=
# Server Configuration
SERVER_PORT=3000

# Abstract Configuration
ABSTRACT_ADDRESS=
ABSTRACT_PRIVATE_KEY=
ABSTRACT_RPC_URL=https://api.testnet.abs.xyz

# Starknet Configuration
STARKNET_ADDRESS=
STARKNET_PRIVATE_KEY=
Expand Down
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@elizaos/client-slack": "workspace:*",
"@elizaos/core": "workspace:*",
"@elizaos/plugin-0g": "workspace:*",
"@elizaos/plugin-abstract": "workspace:*",
"@elizaos/plugin-aptos": "workspace:*",
"@elizaos/plugin-bootstrap": "workspace:*",
"@elizaos/plugin-intiface": "workspace:*",
Expand Down
4 changes: 4 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { suiPlugin } from "@elizaos/plugin-sui";
import { TEEMode, teePlugin } from "@elizaos/plugin-tee";
import { tonPlugin } from "@elizaos/plugin-ton";
import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import { abstractPlugin } from "@elizaos/plugin-abstract";
import Database from "better-sqlite3";
import fs from "fs";
import path from "path";
Expand Down Expand Up @@ -533,6 +534,9 @@ export async function createAgent(
? webhookPlugin
: null,
getSecret(character, "EVM_PROVIDER_URL") ? goatPlugin : null,
getSecret(character, "ABSTRACT_PRIVATE_KEY")
? abstractPlugin
: null,
getSecret(character, "FLOW_ADDRESS") &&
getSecret(character, "FLOW_PRIVATE_KEY")
? flowPlugin
Expand Down
19 changes: 19 additions & 0 deletions packages/plugin-abstract/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@elizaos/plugin-abstract",
"version": "0.1.7-alpha.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"tsup": "^8.3.5",
"web3": "^4.15.0",
"viem": "2.21.53"
},
"scripts": {
"build": "tsup --format esm --dts"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
257 changes: 257 additions & 0 deletions packages/plugin-abstract/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import {
ActionExample,
Content,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
type Action,
elizaLogger,
composeContext,
generateObject,
} from "@elizaos/core";
import { validateAbstractConfig } from "../environment";

import { Address, createWalletClient, erc20Abi, http, parseEther } from "viem";
import { abstractTestnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { eip712WalletActions } from "viem/zksync";
import { z } from "zod";

const TransferSchema = z.object({
tokenAddress: z.string(),
recipient: z.string(),
amount: z.string(),
});

export interface TransferContent extends Content {
tokenAddress: string;
recipient: string;
amount: string | number;
}

export function isTransferContent(
content: TransferContent
): content is TransferContent {
// Validate types
const validTypes =
typeof content.tokenAddress === "string" &&
typeof content.recipient === "string" &&
(typeof content.amount === "string" ||
typeof content.amount === "number");
if (!validTypes) {
return false;
}

// Validate addresses
const validAddresses =
content.tokenAddress.startsWith("0x") &&
content.tokenAddress.length === 42 &&
content.recipient.startsWith("0x") &&
content.recipient.length === 42;

return validAddresses;
}

const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
Here are several frequently used addresses. Use these for the corresponding tokens:
- ETH/eth: 0x000000000000000000000000000000000000800A
- USDC/usdc: 0xe4c7fbb0a626ed208021ccaba6be1566905e2dfc
Example response:
\`\`\`json
{
"tokenAddress": "0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E",
"recipient": "0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
"amount": "1000"
}
\`\`\`
{{recentMessages}}
Given the recent messages, extract the following information about the requested token transfer:
- Token contract address
- Recipient wallet address
- Amount to transfer
Respond with a JSON markdown block containing only the extracted values.`;

const ETH_ADDRESS = "0x000000000000000000000000000000000000800A";
const ERC20_OVERRIDE_INFO = {
"0xe4c7fbb0a626ed208021ccaba6be1566905e2dfc": {
name: "USDC",
decimals: 6,
},
};

export default {
name: "SEND_TOKEN",
similes: [
"TRANSFER_TOKEN_ON_ABSTRACT",
"TRANSFER_TOKENS_ON_ABSTRACT",
"SEND_TOKENS_ON_ABSTRACT",
"SEND_ETH_ON_ABSTRACT",
"PAY_ON_ABSTRACT",
"MOVE_TOKENS_ON_ABSTRACT",
"MOVE_ETH_ON_ABSTRACT",
],
validate: async (runtime: IAgentRuntime, message: Memory) => {
await validateAbstractConfig(runtime);
return true;
},
description: "Transfer tokens from the agent's wallet to another address",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting Abstract SEND_TOKEN handler...");

// Initialize or update state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

// Compose transfer context
const transferContext = composeContext({
state,
template: transferTemplate,
});

// Generate transfer content
const content = (
await generateObject({
runtime,
context: transferContext,
modelClass: ModelClass.SMALL,
schema: TransferSchema,
})
).object as unknown as TransferContent;

// Validate transfer content
if (!isTransferContent(content)) {
console.error("Invalid content for TRANSFER_TOKEN action.");
if (callback) {
callback({
text: "Unable to process transfer request. Invalid content provided.",
content: { error: "Invalid transfer content" },
});
}
return false;
}

try {
const PRIVATE_KEY = runtime.getSetting("ABSTRACT_PRIVATE_KEY")!;
const account = privateKeyToAccount(`0x${PRIVATE_KEY}`);

const walletClient = createWalletClient({
chain: abstractTestnet,
transport: http(),
}).extend(eip712WalletActions());

let hash;
if (
content.tokenAddress.toLowerCase() !== ETH_ADDRESS.toLowerCase()
) {
// Convert amount to proper token decimals
const tokenInfo =
ERC20_OVERRIDE_INFO[content.tokenAddress.toLowerCase()];
const decimals = tokenInfo?.decimals ?? 18; // Default to 18 decimals if not specified
const tokenAmount =
BigInt(content.amount) * BigInt(10 ** decimals);

// Execute ERC20 transfer
hash = await walletClient.writeContract({
account,
chain: abstractTestnet,
address: content.tokenAddress as Address,
abi: erc20Abi,
functionName: "transfer",
args: [content.recipient as Address, tokenAmount],
});
} else {
hash = await walletClient.sendTransaction({
account: account,
chain: abstractTestnet,
to: content.recipient as Address,
value: parseEther(content.amount.toString()),
kzg: undefined,
});
}

elizaLogger.success(
"Transfer completed successfully! Transaction hash: " + hash
);
if (callback) {
callback({
text:
"Transfer completed successfully! Transaction hash: " +
hash,
content: {},
});
}

return true;
} catch (error) {
elizaLogger.error("Error during token transfer:", error);
if (callback) {
callback({
text: `Error transferring tokens: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},

examples: [
[
{
user: "{{user1}}",
content: {
text: "Send 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
},
},
{
user: "{{agent}}",
content: {
text: "Sure, I'll send 100 USDC to that address now.",
action: "SEND_TOKEN",
},
},
{
user: "{{agent}}",
content: {
text: "Successfully sent 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Please send 0.1 ETH to 0xbD8679cf79137042214fA4239b02F4022208EE82",
},
},
{
user: "{{agent}}",
content: {
text: "Of course. Sending 0.1 ETH to that address now.",
action: "SEND_TOKEN",
},
},
{
user: "{{agent}}",
content: {
text: "Successfully sent 0.1 ETH to 0xbD8679cf79137042214fA4239b02F4022208EE82\nTransaction: 0x0b9f23e69ea91ba98926744472717960cc7018d35bc3165bdba6ae41670da0f0",
},
},
],
] as ActionExample[][],
} as Action;
32 changes: 32 additions & 0 deletions packages/plugin-abstract/src/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IAgentRuntime } from "@elizaos/core";
import { z } from "zod";

export const abstractEnvSchema = z.object({
ABSTRACT_ADDRESS: z.string().min(1, "Abstract address is required"),
ABSTRACT_PRIVATE_KEY: z.string().min(1, "Abstract private key is required"),
});

export type AbstractConfig = z.infer<typeof abstractEnvSchema>;

export async function validateAbstractConfig(
runtime: IAgentRuntime
): Promise<AbstractConfig> {
try {
const config = {
ABSTRACT_ADDRESS: runtime.getSetting("ABSTRACT_ADDRESS"),
ABSTRACT_PRIVATE_KEY: runtime.getSetting("ABSTRACT_PRIVATE_KEY"),
};

return abstractEnvSchema.parse(config);
} catch (error) {
if (error instanceof z.ZodError) {
const errorMessages = error.errors
.map((err) => `${err.path.join(".")}: ${err.message}`)
.join("\n");
throw new Error(
`Abstract configuration validation failed:\n${errorMessages}`
);
}
throw error;
}
}
13 changes: 13 additions & 0 deletions packages/plugin-abstract/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Plugin } from "@elizaos/core";

import transfer from "./actions/transfer.ts";

export const abstractPlugin: Plugin = {
name: "abstract",
description: "Abstract Plugin for Eliza",
actions: [transfer],
evaluators: [],
providers: [],
};

export default abstractPlugin;
10 changes: 10 additions & 0 deletions packages/plugin-abstract/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../core/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": [
"src/**/*.ts"
]
}
20 changes: 20 additions & 0 deletions packages/plugin-abstract/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
outDir: "dist",
sourcemap: true,
clean: true,
format: ["esm"], // Ensure you're targeting CommonJS
external: [
"dotenv", // Externalize dotenv to prevent bundling
"fs", // Externalize fs to use Node.js built-in module
"path", // Externalize other built-ins if necessary
"@reflink/reflink",
"@node-llama-cpp",
"https",
"http",
"agentkeepalive",
// Add other modules you want to externalize
],
});
Loading

0 comments on commit 09f0053

Please sign in to comment.