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

[Feature] Add Navi Protocol for supply , borrow , repay and flashloan #3425

Closed
wants to merge 7 commits into from
Closed
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
5 changes: 4 additions & 1 deletion packages/plugin-sui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
"@cetusprotocol/aggregator-sdk": "^0.3.21",
"@elizaos/core": "workspace:*",
"@mysten/sui": "^1.16.0",
"@mysten/sui.js": "^0.49.1",
"axios": "^1.7.9",
"bignumber.js": "9.1.2",
"navi-sdk": "^1.4.27",
"node-cache": "5.1.2",
"tsup": "8.3.5",
"vitest": "2.1.9"
"vitest": "2.1.9",
"zod": "^3.22.4"
},
"devDependencies": {
"@biomejs/biome": "1.5.3",
Expand Down
226 changes: 226 additions & 0 deletions packages/plugin-sui/src/actions/navi-lend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import {
ActionExample,
Content,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
ServiceType,
State,
composeContext,
elizaLogger,
generateObject,
type Action,
} from "@elizaos/core";
import { NaviService } from "../services/navi";
import { z } from "zod";

export interface LendPayload extends Content {
operation: "supply" | "withdraw" | "borrow" | "repay";
token_symbol: string;
amount: string | number;
}

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

Example response:
\`\`\`json
{
"operation": "supply",
"token_symbol": "sui",
"amount": "1"
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the following information about the lending operation:
- Operation type (supply, withdraw, borrow, or repay)
- Token symbol
- Amount to supply/withdraw/borrow/repay

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

export default {
name: "NAVI_LEND",
similes: ["NAVI_SUPPLY", "NAVI_WITHDRAW", "NAVI_BORROW", "NAVI_REPAY"],
validate: async (runtime: IAgentRuntime, message: Memory) => {
console.log("Validating navi lending operation from user:", message.userId);
return true;
},
description: "Supply, withdraw, borrow, or repay tokens from Navi protocol",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting NAVI_LEND handler...");

const service = runtime.getService<NaviService>(ServiceType.TRANSCRIPTION);

if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

const lendSchema = z.object({
operation: z.enum(["supply", "withdraw", "borrow", "repay"]),
token_symbol: z.string(),
amount: z.union([z.string(), z.number()]),
});

const lendContext = composeContext({
state,
template: lendTemplate,
});

const content = await generateObject({
runtime,
context: lendContext,
schema: lendSchema,
modelClass: ModelClass.SMALL,
});

const lendContent = content.object as LendPayload;
elizaLogger.info("Lend content:", lendContent);

try {
// Get token metadata
const tokenInfo = {
symbol: lendContent.token_symbol.toUpperCase(),
address: "", // This will be filled by the SDK
decimal: 9,
};

// Get health factor before operation
const healthFactor = await service.getHealthFactor(service.getAddress());
elizaLogger.info("Current health factor:", healthFactor);

// Get dynamic health factor for the operation
const dynamicHealthFactor = await service.getDynamicHealthFactor(
service.getAddress(),
tokenInfo,
lendContent.operation === "supply" ? Number(lendContent.amount) : 0,
lendContent.operation === "borrow" ? Number(lendContent.amount) : 0,
lendContent.operation === "supply" || lendContent.operation === "borrow"
);
elizaLogger.info("Dynamic health factor:", dynamicHealthFactor);

// Execute the operation
const result = await service.executeOperation(
lendContent.operation,
lendContent.token_symbol,
lendContent.amount
);

callback({
text: `Successfully executed ${lendContent.operation} operation:
- Token: ${lendContent.token_symbol}
- Amount: ${lendContent.amount}
- Current Health Factor: ${healthFactor}
- Projected Health Factor: ${dynamicHealthFactor}
- Transaction: ${JSON.stringify(result)}`,
content: lendContent,
});

return true;
} catch (error) {
elizaLogger.error("Error in lending operation:", error);
callback({
text: `Failed to perform ${lendContent.operation} operation: ${error}`,
content: { error: `Failed to ${lendContent.operation}` },
});
return false;
}
},

examples: [
[
{
user: "{{user1}}",
content: {
text: "I want to supply 1 SUI to Navi",
},
},
{
user: "{{user2}}",
content: {
text: "I'll help you supply 1 SUI to Navi protocol...",
action: "NAVI_LEND",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully supplied 1 SUI to Navi protocol",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Withdraw 0.5 SUI from Navi",
},
},
{
user: "{{user2}}",
content: {
text: "I'll help you withdraw 0.5 SUI from Navi protocol...",
action: "NAVI_LEND",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully withdrew 0.5 SUI from Navi protocol",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Borrow 100 USDC from Navi",
},
},
{
user: "{{user2}}",
content: {
text: "I'll help you borrow 100 USDC from Navi protocol...",
action: "NAVI_LEND",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully borrowed 100 USDC from Navi protocol",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Repay 50 USDC to Navi",
},
},
{
user: "{{user2}}",
content: {
text: "I'll help you repay 50 USDC to Navi protocol...",
action: "NAVI_LEND",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully repaid 50 USDC to Navi protocol",
},
},
],
] as ActionExample[][],
} as Action;
153 changes: 153 additions & 0 deletions packages/plugin-sui/src/actions/navi-pool-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
ActionExample,
Content,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
ServiceType,
State,
composeContext,
elizaLogger,
generateObject,
type Action,
} from "@elizaos/core";
import { NaviService } from "../services/navi";
import { z } from "zod";

export interface PoolInfoPayload extends Content {
token_symbol?: string;
}

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

Example response:
\`\`\`json
{
"token_symbol": "sui"
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the following information about the requested pool information:
- Token symbol (if specified)

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

export default {
name: "GET_NAVI_POOL_INFO",
similes: ["VIEW_NAVI_POOL", "CHECK_NAVI_POOL"],
validate: async (runtime: IAgentRuntime, message: Memory) => {
console.log("Validating navi pool info request from user:", message.userId);
return true;
},
description: "Get information about Navi protocol pools",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting GET_NAVI_POOL_INFO handler...");

const service = runtime.getService<NaviService>(ServiceType.TRANSCRIPTION);

if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

const poolInfoSchema = z.object({
token_symbol: z.string().optional().nullable(),
});

const poolInfoContext = composeContext({
state,
template: poolInfoTemplate,
});

const content = await generateObject({
runtime,
context: poolInfoContext,
schema: poolInfoSchema,
modelClass: ModelClass.SMALL,
});

const poolInfoContent = content.object as PoolInfoPayload;
elizaLogger.info("Pool info content:", poolInfoContent);

try {
const poolInfo = await service.getPoolInfo(
poolInfoContent.token_symbol
? {
symbol: poolInfoContent.token_symbol.toUpperCase(),
address: "", // This will be filled by the SDK
decimal: 9,
}
: undefined
);

callback({
text: `Here's the pool information: ${JSON.stringify(poolInfo, null, 2)}`,
content: poolInfo,
});

return true;
} catch (error) {
elizaLogger.error("Error getting pool info:", error);
callback({
text: `Failed to get pool information: ${error}`,
content: { error: "Failed to get pool information" },
});
return false;
}
},

examples: [
[
{
user: "{{user1}}",
content: {
text: "Show me the SUI pool information in Navi",
},
},
{
user: "{{user2}}",
content: {
text: "I'll help you check the SUI pool information in Navi protocol...",
action: "GET_NAVI_POOL_INFO",
},
},
{
user: "{{user2}}",
content: {
text: "Here's the pool information for SUI: {...}",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "What are all the available pools in Navi?",
},
},
{
user: "{{user2}}",
content: {
text: "I'll help you check all available pools in Navi protocol...",
action: "GET_NAVI_POOL_INFO",
},
},
{
user: "{{user2}}",
content: {
text: "Here are all the available pools: {...}",
},
},
],
] as ActionExample[][],
} as Action;
Loading
Loading