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 stake/unstake/pool-info actions #3177

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
163 changes: 163 additions & 0 deletions packages/plugin-ton/src/actions/getPoolInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import {
elizaLogger,
composeContext,
type Content,
type HandlerCallback,
ModelClass,
generateObject,
type IAgentRuntime,
type Memory,
type State,
} from "@elizaos/core";
import { z } from "zod";
import { initStakingProvider, IStakingProvider } from "../providers/staking";


export interface PoolInfoContent extends Content {
poolId: string;
}

function isPoolInfoContent(content: Content): content is PoolInfoContent {
return typeof content.poolId === "string";
}

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

Example response:
\`\`\`json
{
"poolId": "pool123"
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the pool identifier (poolId) for which to fetch staking pool information.

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

export class GetPoolInfoAction {
constructor(private stakingProvider: IStakingProvider) {}

async getPoolInfo(params: PoolInfoContent): Promise<any> {
elizaLogger.log(`Fetching pool info for pool (${params.poolId})`);
try {
// Call the staking provider's getPoolInfo method.
const poolInfo = await this.stakingProvider.getPoolInfo(
params.poolId,
);
return poolInfo;
} catch (error) {
throw new Error(`Fetching pool info failed: ${error.message}`);
}
}
}

const buildPoolInfoDetails = async (
runtime: IAgentRuntime,
message: Memory,
state: State,
): Promise<PoolInfoContent> => {
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}
const poolInfoSchema = z.object({
poolId: z.string(),
});

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

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

return content.object as PoolInfoContent;
};

export default {
name: "GET_POOL_INFO",
similes: ["FETCH_POOL_INFO", "POOL_DATA", "GET_STAKING_INFO"],
description: "Fetch detailed staking pool information",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: HandlerCallback,
) => {
elizaLogger.log("Starting GET_POOL_INFO handler...");
const poolInfoDetails = await buildPoolInfoDetails(
runtime,
message,
state,
);

if (!isPoolInfoContent(poolInfoDetails)) {
elizaLogger.error("Invalid content for GET_POOL_INFO action.");
if (callback) {
callback({
text: "Invalid pool info details provided.",
content: { error: "Invalid pool info content" },
});
}
return false;
}

try {
const stakingProvider = await initStakingProvider(runtime);
const action = new GetPoolInfoAction(stakingProvider);
const poolInfo = await action.getPoolInfo(poolInfoDetails);

if (callback) {
callback({
text: `Successfully fetched pool info for ${poolInfoDetails.poolId}.`,
content: poolInfo,
});
}
return true;
} catch (error) {
elizaLogger.error("Error fetching pool info:", error);
if (callback) {
callback({
text: `Error fetching pool info: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},
template: getPoolInfoTemplate,
validate: async (runtime: IAgentRuntime) => true,
examples: [
[
{
user: "{{user1}}",
content: {
text: "Get info for pool pool123",
action: "GET_POOL_INFO",
},
},
{
user: "{{user2}}",
content: {
text: "Fetching pool info...",
action: "GET_POOL_INFO",
},
},
{
user: "{{user2}}",
content: {
text: 'Fetched pool info for pool pool123: { "totalStaked": 1000, "rewardRate": 0.05, ...}',
},
},
],
],
};
187 changes: 187 additions & 0 deletions packages/plugin-ton/src/actions/stake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import {
elizaLogger,
composeContext,
type Content,
type HandlerCallback,
ModelClass,
generateObject,
type IAgentRuntime,
type Memory,
type State,
} from "@elizaos/core";
import { z } from "zod";
import { IStakingProvider, StakingProvider } from "../providers/staking";
import { initWalletProvider } from "../providers/wallet";

export interface StakeContent extends Content {
poolId: string;
amount: string | number;
}

function isStakeContent(content: Content): content is StakeContent {
return (
typeof content.poolId === "string" &&
(typeof content.amount === "string" ||
typeof content.amount === "number")
);
}

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

Example response:
\`\`\`json
{
"poolId": "pool123",
"amount": "1.5"
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the following information for staking TON:
- Pool identifier (poolId)
- Amount to stake

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

/**
* Modified StakeAction class that uses the nativeStakingProvider which
* internally leverages the current wallet provider to construct and send
* on-chain transactions.
*/
export class StakeAction {
constructor(
private stakingProvider: IStakingProvider,
) {}

async stake(params: StakeContent): Promise<string | null> {
elizaLogger.log(
`Staking: ${params.amount} TON in pool (${params.poolId}) using wallet provider`,
);
try {
return await this.stakingProvider.stake(params.poolId, Number(params.amount));
} catch (error) {
throw new Error(`Staking failed: ${error.message}`);
}
}
}

const buildStakeDetails = async (
runtime: IAgentRuntime,
message: Memory,
state: State,
): Promise<StakeContent> => {
// 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 stakeSchema = z.object({
poolId: z.string(),
amount: z.union([z.string(), z.number()]),
});

// Compose staking context
const stakeContext = composeContext({
state,
template: stakeTemplate,
});

// Generate stake content with the schema
const content = await generateObject({
runtime,
context: stakeContext,
schema: stakeSchema,
modelClass: ModelClass.SMALL,
});

return content.object as StakeContent;
};

export default {
name: "STAKE_TON",
similes: ["STAKE_TOKENS", "DEPOSIT_TON", "TON_STAKE"],
description: "Stake TON tokens in a specified pool",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: HandlerCallback,
) => {
elizaLogger.log("Starting STAKE_TON handler...");
const stakeDetails = await buildStakeDetails(runtime, message, state);

if (!isStakeContent(stakeDetails)) {
elizaLogger.error("Invalid content for STAKE_TON action.");
if (callback) {
callback({
text: "Invalid staking details provided.",
content: { error: "Invalid staking content" },
});
}
return false;
}

try {

const walletProvider = await initWalletProvider(runtime);
const stakingProvider = new StakingProvider(walletProvider);
// Instantiate StakeAction with the native staking provider.
const action = new StakeAction(stakingProvider);
const txHash = await action.stake(stakeDetails);

if (callback) {
callback({
text: `Successfully staked ${stakeDetails.amount} TON in pool ${stakeDetails.poolId}. Transaction: ${txHash}`,
content: {
success: true,
hash: txHash,
amount: stakeDetails.amount,
poolId: stakeDetails.poolId,
},
});
}
return true;
} catch (error) {
elizaLogger.error("Error during staking:", error);
if (callback) {
callback({
text: `Error staking TON: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},
template: stakeTemplate,
validate: async (runtime: IAgentRuntime) => {
return true;
},
examples: [
[
{
user: "{{user1}}",
content: {
text: "Stake 1.5 TON in pool pool123",
action: "STAKE_TON",
},
},
{
user: "{{user2}}",
content: {
text: "I'll stake 1.5 TON now...",
action: "STAKE_TON",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully staked 1.5 TON in pool pool123, Transaction: abcd1234efgh5678",
},
},
],
],
};
Loading
Loading