Skip to content

Commit 079d848

Browse files
authored
Merge pull request elizaOS#818 from 0xaptosj/j/aptos-plugin
feat: add Aptos plugin
2 parents 5608d1a + 5b019be commit 079d848

15 files changed

+940
-1
lines changed

.env.example

+4
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,7 @@ WHATSAPP_API_VERSION=v17.0 # WhatsApp API version (default: v17.0)
185185
# ICP
186186
INTERNET_COMPUTER_PRIVATE_KEY=
187187
INTERNET_COMPUTER_ADDRESS=
188+
189+
# Aptos
190+
APTOS_PRIVATE_KEY= # Aptos private key
191+
APTOS_NETWORK= # must be one of mainnet, testnet

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@ai16z/client-twitter": "workspace:*",
2727
"@ai16z/eliza": "workspace:*",
2828
"@ai16z/plugin-0g": "workspace:*",
29+
"@ai16z/plugin-aptos": "workspace:*",
2930
"@ai16z/plugin-bootstrap": "workspace:*",
3031
"@ai16z/plugin-buttplug": "workspace:*",
3132
"@ai16z/plugin-coinbase": "workspace:*",

agent/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
AgentRuntime,
1010
CacheManager,
1111
Character,
12+
Clients,
1213
DbCacheAdapter,
1314
FsCacheAdapter,
1415
IAgentRuntime,
@@ -37,6 +38,7 @@ import { imageGenerationPlugin } from "@ai16z/plugin-image-generation";
3738
import { evmPlugin } from "@ai16z/plugin-evm";
3839
import { createNodePlugin } from "@ai16z/plugin-node";
3940
import { solanaPlugin } from "@ai16z/plugin-solana";
41+
import { aptosPlugin, TransferAptosToken } from "@ai16z/plugin-aptos";
4042
import { teePlugin } from "@ai16z/plugin-tee";
4143
import Database from "better-sqlite3";
4244
import fs from "fs";
@@ -393,6 +395,7 @@ export function createAgent(
393395
: []),
394396
getSecret(character, "WALLET_SECRET_SALT") ? teePlugin : null,
395397
getSecret(character, "ALCHEMY_API_KEY") ? goatPlugin : null,
398+
getSecret(character, "APTOS_PRIVATE_KEY") ? aptosPlugin : null,
396399
].filter(Boolean),
397400
providers: [],
398401
actions: [],

packages/plugin-aptos/.npmignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*
2+
3+
!dist/**
4+
!package.json
5+
!readme.md
6+
!tsup.config.ts
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import eslintGlobalConfig from "../../eslint.config.mjs";
2+
3+
export default [...eslintGlobalConfig];

packages/plugin-aptos/package.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@ai16z/plugin-aptos",
3+
"version": "0.1.5-alpha.0",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@ai16z/eliza": "workspace:*",
9+
"@ai16z/plugin-trustdb": "workspace:*",
10+
"@aptos-labs/ts-sdk": "^1.26.0",
11+
"bignumber": "1.1.0",
12+
"bignumber.js": "9.1.2",
13+
"node-cache": "5.1.2",
14+
"tsup": "8.3.5",
15+
"vitest": "2.1.4"
16+
},
17+
"devDependencies": {},
18+
"scripts": {
19+
"build": "tsup --format esm --dts",
20+
"lint": "eslint . --fix",
21+
"test": "vitest run"
22+
},
23+
"peerDependencies": {
24+
"whatwg-url": "7.1.0",
25+
"form-data": "4.0.1"
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { elizaLogger } from "@ai16z/eliza";
2+
import {
3+
ActionExample,
4+
Content,
5+
HandlerCallback,
6+
IAgentRuntime,
7+
Memory,
8+
ModelClass,
9+
State,
10+
type Action,
11+
} from "@ai16z/eliza";
12+
import { composeContext } from "@ai16z/eliza";
13+
import { generateObject } from "@ai16z/eliza";
14+
import {
15+
Account,
16+
Aptos,
17+
AptosConfig,
18+
Ed25519PrivateKey,
19+
Network,
20+
PrivateKey,
21+
PrivateKeyVariants,
22+
} from "@aptos-labs/ts-sdk";
23+
import { walletProvider } from "../providers/wallet";
24+
25+
export interface TransferContent extends Content {
26+
recipient: string;
27+
amount: string | number;
28+
}
29+
30+
function isTransferContent(content: any): content is TransferContent {
31+
console.log("Content for transfer", content);
32+
return (
33+
typeof content.recipient === "string" &&
34+
(typeof content.amount === "string" ||
35+
typeof content.amount === "number")
36+
);
37+
}
38+
39+
const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
40+
41+
Example response:
42+
\`\`\`json
43+
{
44+
"recipient": "0x2badda48c062e861ef17a96a806c451fd296a49f45b272dee17f85b0e32663fd",
45+
"amount": "1000"
46+
}
47+
\`\`\`
48+
49+
{{recentMessages}}
50+
51+
Given the recent messages, extract the following information about the requested token transfer:
52+
- Recipient wallet address
53+
- Amount to transfer
54+
55+
Respond with a JSON markdown block containing only the extracted values.`;
56+
57+
export default {
58+
name: "SEND_TOKEN",
59+
similes: [
60+
"TRANSFER_TOKEN",
61+
"TRANSFER_TOKENS",
62+
"SEND_TOKENS",
63+
"SEND_APT",
64+
"PAY",
65+
],
66+
validate: async (runtime: IAgentRuntime, message: Memory) => {
67+
console.log("Validating apt transfer from user:", message.userId);
68+
//add custom validate logic here
69+
/*
70+
const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || [];
71+
//console.log("Admin IDs from settings:", adminIds);
72+
73+
const isAdmin = adminIds.includes(message.userId);
74+
75+
if (isAdmin) {
76+
//console.log(`Authorized transfer from user: ${message.userId}`);
77+
return true;
78+
}
79+
else
80+
{
81+
//console.log(`Unauthorized transfer attempt from user: ${message.userId}`);
82+
return false;
83+
}
84+
*/
85+
return false;
86+
},
87+
description: "Transfer tokens from the agent's wallet to another address",
88+
handler: async (
89+
runtime: IAgentRuntime,
90+
message: Memory,
91+
state: State,
92+
_options: { [key: string]: unknown },
93+
callback?: HandlerCallback
94+
): Promise<boolean> => {
95+
elizaLogger.log("Starting SEND_TOKEN handler...");
96+
97+
const walletInfo = await walletProvider.get(runtime, message, state);
98+
state.walletInfo = walletInfo;
99+
100+
// Initialize or update state
101+
if (!state) {
102+
state = (await runtime.composeState(message)) as State;
103+
} else {
104+
state = await runtime.updateRecentMessageState(state);
105+
}
106+
107+
// Compose transfer context
108+
const transferContext = composeContext({
109+
state,
110+
template: transferTemplate,
111+
});
112+
113+
// Generate transfer content
114+
const content = await generateObject({
115+
runtime,
116+
context: transferContext,
117+
modelClass: ModelClass.SMALL,
118+
});
119+
120+
// Validate transfer content
121+
if (!isTransferContent(content)) {
122+
console.error("Invalid content for TRANSFER_TOKEN action.");
123+
if (callback) {
124+
callback({
125+
text: "Unable to process transfer request. Invalid content provided.",
126+
content: { error: "Invalid transfer content" },
127+
});
128+
}
129+
return false;
130+
}
131+
132+
try {
133+
const privateKey = runtime.getSetting("APTOS_PRIVATE_KEY");
134+
const aptosAccount = Account.fromPrivateKey({
135+
privateKey: new Ed25519PrivateKey(
136+
PrivateKey.formatPrivateKey(
137+
privateKey,
138+
PrivateKeyVariants.Ed25519
139+
)
140+
),
141+
});
142+
const network = runtime.getSetting("APTOS_NETWORK") as Network;
143+
const aptosClient = new Aptos(
144+
new AptosConfig({
145+
network,
146+
})
147+
);
148+
149+
const APT_DECIMALS = 8;
150+
const adjustedAmount = BigInt(
151+
Number(content.amount) * Math.pow(10, APT_DECIMALS)
152+
);
153+
console.log(
154+
`Transferring: ${content.amount} tokens (${adjustedAmount} base units)`
155+
);
156+
157+
const tx = await aptosClient.transaction.build.simple({
158+
sender: aptosAccount.accountAddress.toStringLong(),
159+
data: {
160+
function: "0x1::aptos_account::transfer",
161+
typeArguments: [],
162+
functionArguments: [content.recipient, adjustedAmount],
163+
},
164+
});
165+
const committedTransaction =
166+
await aptosClient.signAndSubmitTransaction({
167+
signer: aptosAccount,
168+
transaction: tx,
169+
});
170+
const executedTransaction = await aptosClient.waitForTransaction({
171+
transactionHash: committedTransaction.hash,
172+
});
173+
174+
console.log("Transfer successful:", executedTransaction.hash);
175+
176+
if (callback) {
177+
callback({
178+
text: `Successfully transferred ${content.amount} APT to ${content.recipient}, Transaction: ${executedTransaction.hash}`,
179+
content: {
180+
success: true,
181+
hash: executedTransaction.hash,
182+
amount: content.amount,
183+
recipient: content.recipient,
184+
},
185+
});
186+
}
187+
188+
return true;
189+
} catch (error) {
190+
console.error("Error during token transfer:", error);
191+
if (callback) {
192+
callback({
193+
text: `Error transferring tokens: ${error.message}`,
194+
content: { error: error.message },
195+
});
196+
}
197+
return false;
198+
}
199+
},
200+
201+
examples: [
202+
[
203+
{
204+
user: "{{user1}}",
205+
content: {
206+
text: "Send 69 APT tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0",
207+
},
208+
},
209+
{
210+
user: "{{user2}}",
211+
content: {
212+
text: "I'll send 69 APT tokens now...",
213+
action: "SEND_TOKEN",
214+
},
215+
},
216+
{
217+
user: "{{user2}}",
218+
content: {
219+
text: "Successfully sent 69 APT tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0, Transaction: 0x39a8c432d9bdad993a33cc1faf2e9b58fb7dd940c0425f1d6db3997e4b4b05c0",
220+
},
221+
},
222+
],
223+
] as ActionExample[][],
224+
} as Action;
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const APT_DECIMALS = 8;
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { IAgentRuntime } from "@ai16z/eliza";
2+
import { z } from "zod";
3+
4+
export const aptosEnvSchema = z.object({
5+
APTOS_PRIVATE_KEY: z.string().min(1, "Aptos private key is required"),
6+
APTOS_NETWORK: z.enum(["mainnet", "testnet"]),
7+
});
8+
9+
export type AptosConfig = z.infer<typeof aptosEnvSchema>;
10+
11+
export async function validateAptosConfig(
12+
runtime: IAgentRuntime
13+
): Promise<AptosConfig> {
14+
try {
15+
const config = {
16+
APTOS_PRIVATE_KEY:
17+
runtime.getSetting("APTOS_PRIVATE_KEY") ||
18+
process.env.APTOS_PRIVATE_KEY,
19+
APTOS_NETWORK:
20+
runtime.getSetting("APTOS_NETWORK") ||
21+
process.env.APTOS_NETWORK,
22+
};
23+
24+
return aptosEnvSchema.parse(config);
25+
} catch (error) {
26+
if (error instanceof z.ZodError) {
27+
const errorMessages = error.errors
28+
.map((err) => `${err.path.join(".")}: ${err.message}`)
29+
.join("\n");
30+
throw new Error(
31+
`Aptos configuration validation failed:\n${errorMessages}`
32+
);
33+
}
34+
throw error;
35+
}
36+
}

packages/plugin-aptos/src/index.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Plugin } from "@ai16z/eliza";
2+
import transferToken from "./actions/transfer.ts";
3+
import { WalletProvider, walletProvider } from "./providers/wallet.ts";
4+
5+
export { WalletProvider, transferToken as TransferAptosToken };
6+
7+
export const aptosPlugin: Plugin = {
8+
name: "aptos",
9+
description: "Aptos Plugin for Eliza",
10+
actions: [transferToken],
11+
evaluators: [],
12+
providers: [walletProvider],
13+
};
14+
15+
export default aptosPlugin;

0 commit comments

Comments
 (0)