Skip to content

Commit af04019

Browse files
committed
add plugin-sui
1 parent 5a2994e commit af04019

14 files changed

+16671
-19878
lines changed

.env.example

+4
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,7 @@ INTERNET_COMPUTER_ADDRESS=
214214
# Aptos
215215
APTOS_PRIVATE_KEY= # Aptos private key
216216
APTOS_NETWORK= # must be one of mainnet, testnet
217+
218+
# Sui
219+
SUI_PRIVATE_KEY= # Sui Mnemonic Seed Phrase (`sui keytool generate ed25519`)
220+
SUI_NETWORK= # must be one of mainnet, testnet, devnet, localnet

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@ai16z/plugin-node": "workspace:*",
4141
"@ai16z/plugin-solana": "workspace:*",
4242
"@ai16z/plugin-starknet": "workspace:*",
43+
"@ai16z/plugin-sui": "workspace:*",
4344
"@ai16z/plugin-tee": "workspace:*",
4445
"readline": "1.3.0",
4546
"ws": "8.18.0",

agent/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { solanaPlugin } from "@ai16z/plugin-solana";
4444
import { aptosPlugin, TransferAptosToken } from "@ai16z/plugin-aptos";
4545
import { flowPlugin } from "@ai16z/plugin-flow";
4646
import { teePlugin } from "@ai16z/plugin-tee";
47+
import { suiPlugin } from "@ai16z/plugin-sui";
4748
import Database from "better-sqlite3";
4849
import fs from "fs";
4950
import path from "path";
@@ -415,6 +416,7 @@ export function createAgent(
415416
? flowPlugin
416417
: null,
417418
getSecret(character, "APTOS_PRIVATE_KEY") ? aptosPlugin : null,
419+
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
418420
].filter(Boolean),
419421
providers: [],
420422
actions: [],

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+
}
+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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 { generateObjectDEPRECATED } from "@ai16z/eliza";
14+
15+
16+
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";
17+
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
18+
import { SUI_DECIMALS } from "@mysten/sui/utils";
19+
import { Transaction } from "@mysten/sui/transactions";
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: 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_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 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 generateObjectDEPRECATED({
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("SUI_PRIVATE_KEY");
134+
const suiAccount = Ed25519Keypair.fromSecretKey(privateKey);
135+
const network = runtime.getSetting("SUI_NETWORK")
136+
const suiClient = new SuiClient({
137+
url: getFullnodeUrl(network as SuiNetwork),
138+
});
139+
140+
const adjustedAmount = BigInt(
141+
Number(content.amount) * Math.pow(10, SUI_DECIMALS)
142+
);
143+
console.log(
144+
`Transferring: ${content.amount} tokens (${adjustedAmount} base units)`
145+
);
146+
const tx = new Transaction();
147+
const [coin] = tx.splitCoins(tx.gas, [adjustedAmount]);
148+
tx.transferObjects([coin], content.recipient);
149+
const executedTransaction =
150+
await suiClient.signAndExecuteTransaction({
151+
signer: suiAccount,
152+
transaction: tx,
153+
});
154+
155+
console.log("Transfer successful:", executedTransaction.digest);
156+
157+
if (callback) {
158+
callback({
159+
text: `Successfully transferred ${content.amount} SUI to ${content.recipient}, Transaction: ${executedTransaction.digest}`,
160+
content: {
161+
success: true,
162+
hash: executedTransaction.digest,
163+
amount: content.amount,
164+
recipient: content.recipient,
165+
},
166+
});
167+
}
168+
169+
return true;
170+
} catch (error) {
171+
console.error("Error during token transfer:", error);
172+
if (callback) {
173+
callback({
174+
text: `Error transferring tokens: ${error.message}`,
175+
content: { error: error.message },
176+
});
177+
}
178+
return false;
179+
}
180+
},
181+
182+
examples: [
183+
[
184+
{
185+
user: "{{user1}}",
186+
content: {
187+
text: "Send 69 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0",
188+
},
189+
},
190+
{
191+
user: "{{user2}}",
192+
content: {
193+
text: "I'll send 69 SUI tokens now...",
194+
action: "SEND_TOKEN",
195+
},
196+
},
197+
{
198+
user: "{{user2}}",
199+
content: {
200+
text: "Successfully sent 69 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0, Transaction: 0x39a8c432d9bdad993a33cc1faf2e9b58fb7dd940c0425f1d6db3997e4b4b05c0",
201+
},
202+
},
203+
],
204+
] as ActionExample[][],
205+
} as Action;

packages/plugin-sui/src/enviroment.ts

+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 suiEnvSchema = z.object({
5+
SUI_PRIVATE_KEY: z.string().min(1, "Sui private key is required"),
6+
SUI_NETWORK: z.enum(["mainnet", "testnet"]),
7+
});
8+
9+
export type SuiConfig = z.infer<typeof suiEnvSchema>;
10+
11+
export async function validateSuiConfig(
12+
runtime: IAgentRuntime
13+
): Promise<SuiConfig> {
14+
try {
15+
const config = {
16+
SUI_PRIVATE_KEY:
17+
runtime.getSetting("SUI_PRIVATE_KEY") ||
18+
process.env.SUI_PRIVATE_KEY,
19+
SUI_NETWORK:
20+
runtime.getSetting("SUI_NETWORK") ||
21+
process.env.SUI_NETWORK,
22+
};
23+
24+
return suiEnvSchema.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+
`Sui configuration validation failed:\n${errorMessages}`
32+
);
33+
}
34+
throw error;
35+
}
36+
}

packages/plugin-sui/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 TransferSuiToken };
6+
7+
export const suiPlugin: Plugin = {
8+
name: "sui",
9+
description: "Sui Plugin for Eliza",
10+
actions: [transferToken],
11+
evaluators: [],
12+
providers: [walletProvider],
13+
};
14+
15+
export default suiPlugin;

0 commit comments

Comments
 (0)