Skip to content

Commit 8f767c1

Browse files
committed
Merge branch 'jnaulty/add-sui-plugin' of https://github.com/jnaulty/eliza into develop
2 parents dbfceaa + b7bf5c1 commit 8f767c1

16 files changed

+859
-13
lines changed

.env.example

+5-1
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,8 @@ AWS_S3_BUCKET=
300300
AWS_S3_UPLOAD_PATH=
301301

302302
# Deepgram
303-
DEEPGRAM_API_KEY=
303+
DEEPGRAM_API_KEY=
304+
305+
# Sui
306+
SUI_PRIVATE_KEY= # Sui Mnemonic Seed Phrase (`sui keytool generate ed25519`)
307+
SUI_NETWORK= # must be one of mainnet, testnet, devnet, localnet

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@
235235
- New knowledge not being ingested into agent memory after first run [\#614](https://github.com/ai16z/eliza/issues/614)
236236
- 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)
237237
- Non node.js environments have issues building \(workers for instance\) [\#506](https://github.com/ai16z/eliza/issues/506)
238-
- Error when call `generateObjectV2` [\#469](https://github.com/ai16z/eliza/issues/469)
238+
- Error when call `generateObject` [\#469](https://github.com/ai16z/eliza/issues/469)
239239
- Current token.test.ts and videoGeneration.test.ts are throwing errors [\#464](https://github.com/ai16z/eliza/issues/464)
240240
- unable to run defaultcharacter with ModelProviderName.LLAMACLOUD local [\#271](https://github.com/ai16z/eliza/issues/271)
241241
- Incorrect steps in readme for starting eliza [\#270](https://github.com/ai16z/eliza/issues/270)

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@ai16z/plugin-solana": "workspace:*",
4545
"@ai16z/plugin-starknet": "workspace:*",
4646
"@ai16z/plugin-ton": "workspace:*",
47+
"@ai16z/plugin-sui": "workspace:*",
4748
"@ai16z/plugin-tee": "workspace:*",
4849
"@ai16z/plugin-multiversx": "workspace:*",
4950
"@ai16z/plugin-near": "workspace:*",

agent/src/index.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ import { solanaPlugin } from "@ai16z/plugin-solana";
5050
import { TEEMode, teePlugin } from "@ai16z/plugin-tee";
5151
import { tonPlugin } from "@ai16z/plugin-ton";
5252
import { zksyncEraPlugin } from "@ai16z/plugin-zksync-era";
53-
import { nftGenerationPlugin, createNFTApiRouter } from "@ai16z/plugin-nft-generation";
53+
import {
54+
nftGenerationPlugin,
55+
createNFTApiRouter,
56+
} from "@ai16z/plugin-nft-generation";
57+
import { suiPlugin } from "@ai16z/plugin-sui";
5458
import Database from "better-sqlite3";
5559
import fs from "fs";
5660
import path from "path";
@@ -495,8 +499,10 @@ export async function createAgent(
495499
? evmPlugin
496500
: null,
497501
(getSecret(character, "SOLANA_PUBLIC_KEY") ||
498-
(getSecret(character, "WALLET_PUBLIC_KEY") &&
499-
!getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))) &&
502+
(getSecret(character, "WALLET_PUBLIC_KEY") &&
503+
!getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith(
504+
"0x"
505+
))) &&
500506
getSecret(character, "SOLANA_ADMIN_PUBLIC_KEY") &&
501507
getSecret(character, "SOLANA_PRIVATE_KEY") &&
502508
getSecret(character, "SOLANA_ADMIN_PRIVATE_KEY")
@@ -538,6 +544,7 @@ export async function createAgent(
538544
getSecret(character, "MVX_PRIVATE_KEY") ? multiversxPlugin : null,
539545
getSecret(character, "ZKSYNC_PRIVATE_KEY") ? zksyncEraPlugin : null,
540546
getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null,
547+
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
541548
].filter(Boolean),
542549
providers: [],
543550
actions: [],

packages/plugin-nft-generation/src/api.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { createCollection } from "./handlers/createCollection.ts";
55
import { createNFT, createNFTMetadata } from "./handlers/createNFT.ts";
66
import { verifyNFT } from "./handlers/verifyNFT.ts";
77

8-
export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
8+
export function createNFTApiRouter(
9+
agents: Map<string, AgentRuntime>
10+
): express.Router {
911
const router = express.Router();
1012

1113
router.post(
@@ -22,7 +24,7 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
2224
const collectionAddressRes = await createCollection({
2325
runtime,
2426
collectionName: runtime.character.name,
25-
fee
27+
fee,
2628
});
2729

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

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

123-
124124
router.post(
125125
"/api/nft-generation/verify-nft",
126126
async (req: express.Request, res: express.Response) => {
@@ -134,21 +134,21 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
134134
res.status(404).send("Agent not found");
135135
return;
136136
}
137-
const verifyToken = runtime.getSetting('SOLANA_VERIFY_TOKEN')
137+
const verifyToken = runtime.getSetting("SOLANA_VERIFY_TOKEN");
138138
if (token !== verifyToken) {
139139
res.status(401).send(" Access denied for translation");
140140
return;
141141
}
142142
try {
143-
const {success} = await verifyNFT({
143+
const { success } = await verifyNFT({
144144
runtime,
145145
collectionAddress,
146146
NFTAddress,
147147
});
148148

149149
res.json({
150150
success: true,
151-
data: success ? 'verified' : 'unverified',
151+
data: success ? "verified" : "unverified",
152152
});
153153
} catch (e: any) {
154154
console.log(e);
@@ -160,6 +160,5 @@ export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
160160
}
161161
);
162162

163-
164163
return router;
165164
}

packages/plugin-sui/.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

packages/plugin-sui/eslint.config.mjs

+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-sui/package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@ai16z/plugin-sui",
3+
"version": "0.1.5-alpha.5",
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+
"@mysten/sui": "^1.16.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+
"scripts": {
18+
"build": "tsup --format esm --dts",
19+
"lint": "eslint . --fix",
20+
"test": "vitest run"
21+
},
22+
"peerDependencies": {
23+
"form-data": "4.0.1",
24+
"whatwg-url": "7.1.0"
25+
}
26+
}
+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import {
2+
ActionExample,
3+
Content,
4+
HandlerCallback,
5+
IAgentRuntime,
6+
Memory,
7+
ModelClass,
8+
State,
9+
composeContext,
10+
elizaLogger,
11+
generateObject,
12+
type Action,
13+
} from "@ai16z/eliza";
14+
import { z } from "zod";
15+
16+
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";
17+
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
18+
import { Transaction } from "@mysten/sui/transactions";
19+
import { SUI_DECIMALS } from "@mysten/sui/utils";
20+
21+
import { walletProvider } from "../providers/wallet";
22+
23+
type SuiNetwork = "mainnet" | "testnet" | "devnet" | "localnet";
24+
25+
export interface TransferContent extends Content {
26+
recipient: string;
27+
amount: string | number;
28+
}
29+
30+
function isTransferContent(content: Content): 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": "0xaa000b3651bd1e57554ebd7308ca70df7c8c0e8e09d67123cc15c8a8a79342b3",
45+
"amount": "1"
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_SUI",
64+
"PAY",
65+
],
66+
validate: async (runtime: IAgentRuntime, message: Memory) => {
67+
console.log("Validating sui 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 true;
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+
// Define the schema for the expected output
108+
const transferSchema = z.object({
109+
recipient: z.string(),
110+
amount: z.union([z.string(), z.number()]),
111+
});
112+
113+
// Compose transfer context
114+
const transferContext = composeContext({
115+
state,
116+
template: transferTemplate,
117+
});
118+
119+
// Generate transfer content with the schema
120+
const content = await generateObject({
121+
runtime,
122+
context: transferContext,
123+
schema: transferSchema,
124+
modelClass: ModelClass.SMALL,
125+
});
126+
127+
const transferContent = content.object as TransferContent;
128+
129+
// Validate transfer content
130+
if (!isTransferContent(transferContent)) {
131+
console.error("Invalid content for TRANSFER_TOKEN action.");
132+
if (callback) {
133+
callback({
134+
text: "Unable to process transfer request. Invalid content provided.",
135+
content: { error: "Invalid transfer content" },
136+
});
137+
}
138+
return false;
139+
}
140+
141+
try {
142+
const privateKey = runtime.getSetting("SUI_PRIVATE_KEY");
143+
const suiAccount = Ed25519Keypair.deriveKeypair(privateKey);
144+
const network = runtime.getSetting("SUI_NETWORK");
145+
const suiClient = new SuiClient({
146+
url: getFullnodeUrl(network as SuiNetwork),
147+
});
148+
149+
const adjustedAmount = BigInt(
150+
Number(transferContent.amount) * Math.pow(10, SUI_DECIMALS)
151+
);
152+
console.log(
153+
`Transferring: ${transferContent.amount} tokens (${adjustedAmount} base units)`
154+
);
155+
const tx = new Transaction();
156+
const [coin] = tx.splitCoins(tx.gas, [adjustedAmount]);
157+
tx.transferObjects([coin], transferContent.recipient);
158+
const executedTransaction =
159+
await suiClient.signAndExecuteTransaction({
160+
signer: suiAccount,
161+
transaction: tx,
162+
});
163+
164+
console.log("Transfer successful:", executedTransaction.digest);
165+
166+
if (callback) {
167+
callback({
168+
text: `Successfully transferred ${transferContent.amount} SUI to ${transferContent.recipient}, Transaction: ${executedTransaction.digest}`,
169+
content: {
170+
success: true,
171+
hash: executedTransaction.digest,
172+
amount: transferContent.amount,
173+
recipient: transferContent.recipient,
174+
},
175+
});
176+
}
177+
178+
return true;
179+
} catch (error) {
180+
console.error("Error during token transfer:", error);
181+
if (callback) {
182+
callback({
183+
text: `Error transferring tokens: ${error.message}`,
184+
content: { error: error.message },
185+
});
186+
}
187+
return false;
188+
}
189+
},
190+
191+
examples: [
192+
[
193+
{
194+
user: "{{user1}}",
195+
content: {
196+
text: "Send 1 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0",
197+
},
198+
},
199+
{
200+
user: "{{user2}}",
201+
content: {
202+
text: "I'll send 1 SUI tokens now...",
203+
action: "SEND_TOKEN",
204+
},
205+
},
206+
{
207+
user: "{{user2}}",
208+
content: {
209+
text: "Successfully sent 1 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0, Transaction: 0x39a8c432d9bdad993a33cc1faf2e9b58fb7dd940c0425f1d6db3997e4b4b05c0",
210+
},
211+
},
212+
],
213+
] as ActionExample[][],
214+
} as Action;

0 commit comments

Comments
 (0)