Skip to content

Commit

Permalink
Merge branch 'jnaulty/add-sui-plugin' of https://github.com/jnaulty/e…
Browse files Browse the repository at this point in the history
…liza into develop
  • Loading branch information
lalalune committed Dec 14, 2024
2 parents dbfceaa + b7bf5c1 commit 8f767c1
Show file tree
Hide file tree
Showing 16 changed files with 859 additions and 13 deletions.
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,8 @@ AWS_S3_BUCKET=
AWS_S3_UPLOAD_PATH=

# Deepgram
DEEPGRAM_API_KEY=
DEEPGRAM_API_KEY=

# Sui
SUI_PRIVATE_KEY= # Sui Mnemonic Seed Phrase (`sui keytool generate ed25519`)
SUI_NETWORK= # must be one of mainnet, testnet, devnet, localnet
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@
- New knowledge not being ingested into agent memory after first run [\#614](https://github.com/ai16z/eliza/issues/614)
- Tests failing - token.test.ts failing because it is commented out. Cache and goals tests are failing because jest is now switched with vitest [\#519](https://github.com/ai16z/eliza/issues/519)
- Non node.js environments have issues building \(workers for instance\) [\#506](https://github.com/ai16z/eliza/issues/506)
- Error when call `generateObjectV2` [\#469](https://github.com/ai16z/eliza/issues/469)
- Error when call `generateObject` [\#469](https://github.com/ai16z/eliza/issues/469)
- Current token.test.ts and videoGeneration.test.ts are throwing errors [\#464](https://github.com/ai16z/eliza/issues/464)
- unable to run defaultcharacter with ModelProviderName.LLAMACLOUD local [\#271](https://github.com/ai16z/eliza/issues/271)
- Incorrect steps in readme for starting eliza [\#270](https://github.com/ai16z/eliza/issues/270)
Expand Down
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@ai16z/plugin-solana": "workspace:*",
"@ai16z/plugin-starknet": "workspace:*",
"@ai16z/plugin-ton": "workspace:*",
"@ai16z/plugin-sui": "workspace:*",
"@ai16z/plugin-tee": "workspace:*",
"@ai16z/plugin-multiversx": "workspace:*",
"@ai16z/plugin-near": "workspace:*",
Expand Down
13 changes: 10 additions & 3 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ import { solanaPlugin } from "@ai16z/plugin-solana";
import { TEEMode, teePlugin } from "@ai16z/plugin-tee";
import { tonPlugin } from "@ai16z/plugin-ton";
import { zksyncEraPlugin } from "@ai16z/plugin-zksync-era";
import { nftGenerationPlugin, createNFTApiRouter } from "@ai16z/plugin-nft-generation";
import {
nftGenerationPlugin,
createNFTApiRouter,
} from "@ai16z/plugin-nft-generation";
import { suiPlugin } from "@ai16z/plugin-sui";
import Database from "better-sqlite3";
import fs from "fs";
import path from "path";
Expand Down Expand Up @@ -495,8 +499,10 @@ export async function createAgent(
? evmPlugin
: null,
(getSecret(character, "SOLANA_PUBLIC_KEY") ||
(getSecret(character, "WALLET_PUBLIC_KEY") &&
!getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))) &&
(getSecret(character, "WALLET_PUBLIC_KEY") &&
!getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith(
"0x"
))) &&
getSecret(character, "SOLANA_ADMIN_PUBLIC_KEY") &&
getSecret(character, "SOLANA_PRIVATE_KEY") &&
getSecret(character, "SOLANA_ADMIN_PRIVATE_KEY")
Expand Down Expand Up @@ -538,6 +544,7 @@ export async function createAgent(
getSecret(character, "MVX_PRIVATE_KEY") ? multiversxPlugin : null,
getSecret(character, "ZKSYNC_PRIVATE_KEY") ? zksyncEraPlugin : null,
getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null,
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
15 changes: 7 additions & 8 deletions packages/plugin-nft-generation/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { createCollection } from "./handlers/createCollection.ts";
import { createNFT, createNFTMetadata } from "./handlers/createNFT.ts";
import { verifyNFT } from "./handlers/verifyNFT.ts";

export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
export function createNFTApiRouter(
agents: Map<string, AgentRuntime>
): express.Router {
const router = express.Router();

router.post(
Expand All @@ -22,7 +24,7 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
const collectionAddressRes = await createCollection({
runtime,
collectionName: runtime.character.name,
fee
fee,
});

res.json({
Expand All @@ -39,7 +41,6 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
}
);


router.post(
"/api/nft-generation/create-nft-metadata",
async (req: express.Request, res: express.Response) => {
Expand Down Expand Up @@ -120,7 +121,6 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
}
);


router.post(
"/api/nft-generation/verify-nft",
async (req: express.Request, res: express.Response) => {
Expand All @@ -134,21 +134,21 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
res.status(404).send("Agent not found");
return;
}
const verifyToken = runtime.getSetting('SOLANA_VERIFY_TOKEN')
const verifyToken = runtime.getSetting("SOLANA_VERIFY_TOKEN");
if (token !== verifyToken) {
res.status(401).send(" Access denied for translation");
return;
}
try {
const {success} = await verifyNFT({
const { success } = await verifyNFT({
runtime,
collectionAddress,
NFTAddress,
});

res.json({
success: true,
data: success ? 'verified' : 'unverified',
data: success ? "verified" : "unverified",
});
} catch (e: any) {
console.log(e);
Expand All @@ -160,6 +160,5 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
}
);


return router;
}
6 changes: 6 additions & 0 deletions packages/plugin-sui/.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-sui/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];
26 changes: 26 additions & 0 deletions packages/plugin-sui/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@ai16z/plugin-sui",
"version": "0.1.5-alpha.5",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@ai16z/eliza": "workspace:*",
"@ai16z/plugin-trustdb": "workspace:*",
"@mysten/sui": "^1.16.0",
"bignumber": "1.1.0",
"bignumber.js": "9.1.2",
"node-cache": "5.1.2",
"tsup": "8.3.5",
"vitest": "2.1.4"
},
"scripts": {
"build": "tsup --format esm --dts",
"lint": "eslint . --fix",
"test": "vitest run"
},
"peerDependencies": {
"form-data": "4.0.1",
"whatwg-url": "7.1.0"
}
}
214 changes: 214 additions & 0 deletions packages/plugin-sui/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import {
ActionExample,
Content,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
composeContext,
elizaLogger,
generateObject,
type Action,
} from "@ai16z/eliza";
import { z } from "zod";

import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { Transaction } from "@mysten/sui/transactions";
import { SUI_DECIMALS } from "@mysten/sui/utils";

import { walletProvider } from "../providers/wallet";

type SuiNetwork = "mainnet" | "testnet" | "devnet" | "localnet";

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

function isTransferContent(content: Content): content is TransferContent {
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": "0xaa000b3651bd1e57554ebd7308ca70df7c8c0e8e09d67123cc15c8a8a79342b3",
"amount": "1"
}
\`\`\`
{{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_SUI",
"PAY",
],
validate: async (runtime: IAgentRuntime, message: Memory) => {
console.log("Validating sui 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 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 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);
}

// Define the schema for the expected output
const transferSchema = z.object({
recipient: z.string(),
amount: z.union([z.string(), z.number()]),
});

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

// Generate transfer content with the schema
const content = await generateObject({
runtime,
context: transferContext,
schema: transferSchema,
modelClass: ModelClass.SMALL,
});

const transferContent = content.object as TransferContent;

// Validate transfer content
if (!isTransferContent(transferContent)) {
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("SUI_PRIVATE_KEY");
const suiAccount = Ed25519Keypair.deriveKeypair(privateKey);
const network = runtime.getSetting("SUI_NETWORK");
const suiClient = new SuiClient({
url: getFullnodeUrl(network as SuiNetwork),
});

const adjustedAmount = BigInt(
Number(transferContent.amount) * Math.pow(10, SUI_DECIMALS)
);
console.log(
`Transferring: ${transferContent.amount} tokens (${adjustedAmount} base units)`
);
const tx = new Transaction();
const [coin] = tx.splitCoins(tx.gas, [adjustedAmount]);
tx.transferObjects([coin], transferContent.recipient);
const executedTransaction =
await suiClient.signAndExecuteTransaction({
signer: suiAccount,
transaction: tx,
});

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

if (callback) {
callback({
text: `Successfully transferred ${transferContent.amount} SUI to ${transferContent.recipient}, Transaction: ${executedTransaction.digest}`,
content: {
success: true,
hash: executedTransaction.digest,
amount: transferContent.amount,
recipient: transferContent.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 1 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0",
},
},
{
user: "{{user2}}",
content: {
text: "I'll send 1 SUI tokens now...",
action: "SEND_TOKEN",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully sent 1 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0, Transaction: 0x39a8c432d9bdad993a33cc1faf2e9b58fb7dd940c0425f1d6db3997e4b4b05c0",
},
},
],
] as ActionExample[][],
} as Action;
Loading

0 comments on commit 8f767c1

Please sign in to comment.