Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Aptos plugin #818

Merged
merged 5 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,7 @@ WHATSAPP_API_VERSION=v17.0 # WhatsApp API version (default: v17.0)
# ICP
INTERNET_COMPUTER_PRIVATE_KEY=
INTERNET_COMPUTER_ADDRESS=

# Aptos
APTOS_PRIVATE_KEY= # Aptos private key
APTOS_NETWORK= # must be one of mainnet, testnet
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@ai16z/client-twitter": "workspace:*",
"@ai16z/eliza": "workspace:*",
"@ai16z/plugin-0g": "workspace:*",
"@ai16z/plugin-aptos": "workspace:*",
"@ai16z/plugin-bootstrap": "workspace:*",
"@ai16z/plugin-buttplug": "workspace:*",
"@ai16z/plugin-coinbase": "workspace:*",
Expand Down
3 changes: 3 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AgentRuntime,
CacheManager,
Character,
Clients,
DbCacheAdapter,
FsCacheAdapter,
IAgentRuntime,
Expand Down Expand Up @@ -36,6 +37,7 @@ import { imageGenerationPlugin } from "@ai16z/plugin-image-generation";
import { evmPlugin } from "@ai16z/plugin-evm";
import { createNodePlugin } from "@ai16z/plugin-node";
import { solanaPlugin } from "@ai16z/plugin-solana";
import { aptosPlugin, TransferAptosToken } from "@ai16z/plugin-aptos";
import { teePlugin } from "@ai16z/plugin-tee";
import Database from "better-sqlite3";
import fs from "fs";
Expand Down Expand Up @@ -392,6 +394,7 @@ export function createAgent(
: []),
getSecret(character, "WALLET_SECRET_SALT") ? teePlugin : null,
getSecret(character, "ALCHEMY_API_KEY") ? goatPlugin : null,
getSecret(character, "APTOS_PRIVATE_KEY") ? aptosPlugin : null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-aptos/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*

!dist/**
!package.json
!readme.md
!tsup.config.ts
3 changes: 3 additions & 0 deletions packages/plugin-aptos/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import eslintGlobalConfig from "../../eslint.config.mjs";

export default [...eslintGlobalConfig];
27 changes: 27 additions & 0 deletions packages/plugin-aptos/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@ai16z/plugin-aptos",
"version": "0.1.5-alpha.0",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@ai16z/eliza": "workspace:*",
"@ai16z/plugin-trustdb": "workspace:*",
"@aptos-labs/ts-sdk": "^1.26.0",
"bignumber": "1.1.0",
"bignumber.js": "9.1.2",
"node-cache": "5.1.2",
"tsup": "8.3.5",
"vitest": "2.1.4"
},
"devDependencies": {},
"scripts": {
"build": "tsup --format esm --dts",
"lint": "eslint . --fix",
"test": "vitest run"
},
"peerDependencies": {
"whatwg-url": "7.1.0",
"form-data": "4.0.1"
}
}
224 changes: 224 additions & 0 deletions packages/plugin-aptos/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { elizaLogger } from "@ai16z/eliza";
import {
ActionExample,
Content,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
type Action,
} from "@ai16z/eliza";
import { composeContext } from "@ai16z/eliza";
import { generateObject } from "@ai16z/eliza";
import {
Account,
Aptos,
AptosConfig,
Ed25519PrivateKey,
Network,
PrivateKey,
PrivateKeyVariants,
} from "@aptos-labs/ts-sdk";
import { walletProvider } from "../providers/wallet";

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

function isTransferContent(content: any): content is TransferContent {

Check warning on line 30 in packages/plugin-aptos/src/actions/transfer.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type
console.log("Content for transfer", content);
return (
typeof content.recipient === "string" &&
(typeof content.amount === "string" ||
typeof content.amount === "number")
);
}

const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.

Example response:
\`\`\`json
{
"recipient": "0x2badda48c062e861ef17a96a806c451fd296a49f45b272dee17f85b0e32663fd",
"amount": "1000"
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the following information about the requested token transfer:
- Recipient wallet address
- Amount to transfer

Respond with a JSON markdown block containing only the extracted values.`;

export default {
name: "SEND_TOKEN",
similes: [
"TRANSFER_TOKEN",
"TRANSFER_TOKENS",
"SEND_TOKENS",
"SEND_APT",
"PAY",
],
validate: async (runtime: IAgentRuntime, message: Memory) => {
console.log("Validating apt transfer from user:", message.userId);
//add custom validate logic here
/*
const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || [];
//console.log("Admin IDs from settings:", adminIds);

const isAdmin = adminIds.includes(message.userId);

if (isAdmin) {
//console.log(`Authorized transfer from user: ${message.userId}`);
return true;
}
else
{
//console.log(`Unauthorized transfer attempt from user: ${message.userId}`);
return false;
}
*/
return false;
},
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 SEND_TOKEN handler...");

const walletInfo = await walletProvider.get(runtime, message, state);
state.walletInfo = walletInfo;

// 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,
});

// 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 privateKey = runtime.getSetting("APTOS_PRIVATE_KEY");
const aptosAccount = Account.fromPrivateKey({
privateKey: new Ed25519PrivateKey(
PrivateKey.formatPrivateKey(
privateKey,
PrivateKeyVariants.Ed25519
)
),
});
const network = runtime.getSetting("APTOS_NETWORK") as Network;
const aptosClient = new Aptos(
new AptosConfig({
network,
})
);

const APT_DECIMALS = 8;
const adjustedAmount = BigInt(
Number(content.amount) * Math.pow(10, APT_DECIMALS)
);
console.log(
`Transferring: ${content.amount} tokens (${adjustedAmount} base units)`
);

const tx = await aptosClient.transaction.build.simple({
sender: aptosAccount.accountAddress.toStringLong(),
data: {
function: "0x1::aptos_account::transfer",
typeArguments: [],
functionArguments: [content.recipient, adjustedAmount],
},
});
const committedTransaction =
await aptosClient.signAndSubmitTransaction({
signer: aptosAccount,
transaction: tx,
});
const executedTransaction = await aptosClient.waitForTransaction({
transactionHash: committedTransaction.hash,
});

console.log("Transfer successful:", executedTransaction.hash);

if (callback) {
callback({
text: `Successfully transferred ${content.amount} APT to ${content.recipient}, Transaction: ${executedTransaction.hash}`,
content: {
success: true,
hash: executedTransaction.hash,
amount: content.amount,
recipient: content.recipient,
},
});
}

return true;
} catch (error) {
console.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 69 APT tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0",
},
},
{
user: "{{user2}}",
content: {
text: "I'll send 69 APT tokens now...",
action: "SEND_TOKEN",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully sent 69 APT tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0, Transaction: 0x39a8c432d9bdad993a33cc1faf2e9b58fb7dd940c0425f1d6db3997e4b4b05c0",
},
},
],
] as ActionExample[][],
} as Action;
1 change: 1 addition & 0 deletions packages/plugin-aptos/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const APT_DECIMALS = 8;
36 changes: 36 additions & 0 deletions packages/plugin-aptos/src/enviroment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { IAgentRuntime } from "@ai16z/eliza";
import { z } from "zod";

export const aptosEnvSchema = z.object({
APTOS_PRIVATE_KEY: z.string().min(1, "Aptos private key is required"),
APTOS_NETWORK: z.enum(["mainnet", "testnet"]),
});

export type AptosConfig = z.infer<typeof aptosEnvSchema>;

export async function validateAptosConfig(
runtime: IAgentRuntime
): Promise<AptosConfig> {
try {
const config = {
APTOS_PRIVATE_KEY:
runtime.getSetting("APTOS_PRIVATE_KEY") ||
process.env.APTOS_PRIVATE_KEY,
APTOS_NETWORK:
runtime.getSetting("APTOS_NETWORK") ||
process.env.APTOS_NETWORK,
};

return aptosEnvSchema.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(
`Aptos configuration validation failed:\n${errorMessages}`
);
}
throw error;
}
}
15 changes: 15 additions & 0 deletions packages/plugin-aptos/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Plugin } from "@ai16z/eliza";
import transferToken from "./actions/transfer.ts";
import { WalletProvider, walletProvider } from "./providers/wallet.ts";

export { WalletProvider, transferToken as TransferAptosToken };

export const aptosPlugin: Plugin = {
name: "aptos",
description: "Aptos Plugin for Eliza",
actions: [transferToken],
evaluators: [],
providers: [walletProvider],
};

export default aptosPlugin;
Loading
Loading