diff --git a/.env.example b/.env.example index e8004fdf..28424225 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,5 @@ OPENAI_API_KEY= RPC_URL= SOLANA_PRIVATE_KEY= JUPITER_REFERRAL_ACCOUNT= -JUPITER_FEE_BPS= \ No newline at end of file +JUPITER_FEE_BPS= +FLEXLEND_API_KEY= \ No newline at end of file diff --git a/src/actions/index.ts b/src/actions/index.ts index 60e7df4f..6dd866fe 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -13,6 +13,7 @@ import stakeWithJupAction from "./stakeWithJup"; import stakeWithSolayerAction from "./stakeWithSolayer"; import registerDomainAction from "./registerDomain"; import lendAssetAction from "./lendAsset"; +import withdrawAssetAction from "./withdrawAsset"; import createGibworkTaskAction from "./createGibworkTask"; import resolveSolDomainAction from "./resolveSolDomain"; import pythFetchPriceAction from "./pythFetchPrice"; @@ -43,9 +44,10 @@ export const ACTIONS = { GET_TPS_ACTION: getTPSAction, FETCH_PRICE_ACTION: fetchPriceAction, STAKE_WITH_JUP_ACTION: stakeWithJupAction, - STAKE_WITH_SOLAYER_ACTION: stakeWithSolayerAction, + STAKE_WITH_SOLAYER_ACTION : stakeWithSolayerAction, REGISTER_DOMAIN_ACTION: registerDomainAction, LEND_ASSET_ACTION: lendAssetAction, + WITHDRAW_ASSET_ACTION: withdrawAssetAction, CREATE_GIBWORK_TASK_ACTION: createGibworkTaskAction, RESOLVE_SOL_DOMAIN_ACTION: resolveSolDomainAction, PYTH_FETCH_PRICE_ACTION: pythFetchPriceAction, diff --git a/src/actions/lendAsset.ts b/src/actions/lendAsset.ts index 9ceb20ee..4250ad1f 100644 --- a/src/actions/lendAsset.ts +++ b/src/actions/lendAsset.ts @@ -13,11 +13,12 @@ const lendAssetAction: Action = { "deposit usdc", "lending", ], - description: "Lend USDC tokens to earn yield using Lulo protocol", + description: "Lend SPL tokens to earn yield using Lulo protocol", examples: [ [ { input: { + mintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: 100, }, output: { @@ -30,18 +31,20 @@ const lendAssetAction: Action = { ], ], schema: z.object({ - amount: z.number().positive().describe("Amount of USDC to lend"), + mintAddress: z.string().describe("SPL Mint address"), + amount: z.number().positive().describe("Amount to lend"), }), handler: async (agent: SolanaAgentKit, input: Record) => { try { + const mintAddress = input.mintAddress as string; const amount = input.amount as number; - const response = await lendAsset(agent, amount); + const response = await lendAsset(agent, mintAddress, amount); return { status: "success", signature: response, - message: `Successfully lent ${amount} USDC`, + message: `Successfully lent ${amount} of token ${mintAddress}`, }; } catch (error: any) { return { diff --git a/src/actions/withdrawAsset.ts b/src/actions/withdrawAsset.ts new file mode 100644 index 00000000..334c3178 --- /dev/null +++ b/src/actions/withdrawAsset.ts @@ -0,0 +1,54 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { withdrawAsset } from "../tools"; + +const withdrawAssetAction: Action = { + name: "WITHDRAW_ASSET", + similes: [ + "withdraw usdc", + "withdraw with lulo", + ], + description: "Withdraw SPL tokens using Lulo protocol", + examples: [ + [ + { + input: { + mintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + amount: 100, + }, + output: { + status: "success", + signature: "4xKpN2...", + message: "Successfully withdraw 100 USDC", + }, + explanation: "Withdraw 100 USDC on Lulo", + }, + ], + ], + schema: z.object({ + mintAddress: z.string().describe("SPL Mint address"), + amount: z.number().positive().describe("Amount to lend"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const mintAddress = input.mintAddress as string; + const amount = input.amount as number; + + const response = await withdrawAsset(agent, mintAddress, amount); + + return { + status: "success", + signature: response, + message: `Successfully withdraw ${amount} of token ${mintAddress}`, + }; + } catch (error: any) { + return { + status: "error", + message: `Withdraw failed: ${error.message}`, + }; + } + }, +}; + +export default withdrawAssetAction; diff --git a/src/agent/index.ts b/src/agent/index.ts index e82a464d..49d30479 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -14,6 +14,7 @@ import { getPrimaryDomain, launchPumpFunToken, lendAsset, + withdrawAsset, mintCollectionNFT, openbookCreateMarket, manifestCreateMarket, @@ -250,8 +251,12 @@ export class SolanaAgentKit { }); } - async lendAssets(amount: number): Promise { - return lendAsset(this, amount); + async lendAssets(mintAddress: string, amount: number): Promise { + return lendAsset(this, mintAddress, amount); + } + + async withdrawAssets(mintAddress: string, amount: number): Promise { + return withdrawAsset(this, mintAddress, amount); } async getTPS(): Promise { diff --git a/src/langchain/index.ts b/src/langchain/index.ts index ac6319f5..d566d9c4 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -887,9 +887,10 @@ export class SolanaCreateImageTool extends Tool { export class SolanaLendAssetTool extends Tool { name = "solana_lend_asset"; - description = `Lend idle USDC for yield using Lulo. ( only USDC is supported ) + description = `Lend token for yield using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP) - Inputs (input is a json string): + Inputs: + mintAddress: string, eg "So11111111111111111111111111111111111111112" (required) amount: number, eg 1, 0.01 (required)`; constructor(private solanaKit: SolanaAgentKit) { @@ -898,9 +899,11 @@ export class SolanaLendAssetTool extends Tool { async _call(input: string): Promise { try { - const amount = JSON.parse(input).amount || input; + const parsedInput = JSON.parse(input); + const mintAddress = parsedInput.mintAddress + const amount = parsedInput.amount; - const tx = await this.solanaKit.lendAssets(amount); + const tx = await this.solanaKit.lendAssets(mintAddress, amount); return JSON.stringify({ status: "success", @@ -918,6 +921,42 @@ export class SolanaLendAssetTool extends Tool { } } +export class SolanaWithdrawAssetTool extends Tool { + name = "solana_withdraw_asset"; + description = `Withdraw token USDC using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP) + + Inputs (input is a json string): + mintAddress: string, eg "So11111111111111111111111111111111111111112" (required) + amount: number, eg 1, 0.01 (required)`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + async _call(input: string): Promise { + try { + const parsedInput = JSON.parse(input); + const mintAddress = parsedInput.mintAddress + const amount = parsedInput.amount; + + const tx = await this.solanaKit.withdrawAssets(mintAddress, amount); + + return JSON.stringify({ + status: "success", + message: "Asset withdraw successfully", + transaction: tx, + amount, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + export class SolanaTPSCalculatorTool extends Tool { name = "solana_get_tps"; description = "Get the current TPS of the Solana network"; @@ -2139,6 +2178,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaPumpfunTokenLaunchTool(solanaKit), new SolanaCreateImageTool(solanaKit), new SolanaLendAssetTool(solanaKit), + new SolanaWithdrawAssetTool(solanaKit), new SolanaTPSCalculatorTool(solanaKit), new SolanaStakeTool(solanaKit), new SolanaRestakeTool(solanaKit), diff --git a/src/tools/index.ts b/src/tools/index.ts index 1f6e9f5e..1fb69f3a 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -46,4 +46,5 @@ export * from "./stake_with_solayer"; export * from "./tensor_trade"; export * from "./trade"; export * from "./transfer"; +export * from "./withdraw"; export * from "./withdraw_all"; diff --git a/src/tools/lend.ts b/src/tools/lend.ts index 732f00c3..8044207a 100644 --- a/src/tools/lend.ts +++ b/src/tools/lend.ts @@ -4,32 +4,37 @@ import { SolanaAgentKit } from "../index"; /** * Lend tokens for yields using Lulo * @param agent SolanaAgentKit instance - * @param amount Amount of USDC to lend + * @param mintAddress SPL Mint address + * @param amount Amount to lend * @returns Transaction signature */ export async function lendAsset( agent: SolanaAgentKit, + mintAddress: string, amount: number, ): Promise { try { const response = await fetch( - `https://blink.lulo.fi/actions?amount=${amount}&symbol=USDC`, + `https://api.flexlend.fi/generate/account/deposit?priorityFee=50000`, { method: "POST", headers: { "Content-Type": "application/json", + "x-wallet-pubkey": agent.wallet.publicKey.toBase58(), + "x-api-key": process.env.FLEXLEND_API_KEY! }, body: JSON.stringify({ - account: agent.wallet.publicKey.toBase58(), + owner: agent.wallet.publicKey.toBase58(), + mintAddress: mintAddress, + depositAmount: amount.toString(), }), }, ); - - const data = await response.json(); + const { data: { transactionMeta } } = await response.json() // Deserialize the transaction const luloTxn = VersionedTransaction.deserialize( - Buffer.from(data.transaction, "base64"), + Buffer.from(transactionMeta[0].transaction, "base64"), ); // Get a recent blockhash and set it diff --git a/src/tools/withdraw.ts b/src/tools/withdraw.ts new file mode 100644 index 00000000..beb80b87 --- /dev/null +++ b/src/tools/withdraw.ts @@ -0,0 +1,65 @@ +import { VersionedTransaction } from "@solana/web3.js"; +import { SolanaAgentKit } from "../index"; + +/** + * Withdraw tokens for yields using Lulo + * @param agent SolanaAgentKit instance + * @param mintAddress SPL Mint address + * @param amount Amount to withdraw + * @returns Transaction signature + */ +export async function withdrawAsset( + agent: SolanaAgentKit, + mintAddress: string, + amount: number, +): Promise { + try { + const response = await fetch( + `https://api.flexlend.fi/generate/account/withdraw?priorityFee=50000`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-wallet-pubkey": agent.wallet.publicKey.toBase58(), + "x-api-key": process.env.FLEXLEND_API_KEY! + }, + body: JSON.stringify({ + owner: agent.wallet.publicKey.toBase58(), + mintAddress: mintAddress, + depositAmount: amount, + }), + }, + ); + + const { data: { transactionMeta } } = await response.json() + + // Deserialize the transaction + const luloTxn = VersionedTransaction.deserialize( + Buffer.from(transactionMeta[0].transaction, "base64"), + ); + + // Get a recent blockhash and set it + const { blockhash } = await agent.connection.getLatestBlockhash(); + luloTxn.message.recentBlockhash = blockhash; + + // Sign and send transaction + luloTxn.sign([agent.wallet]); + + const signature = await agent.connection.sendTransaction(luloTxn, { + preflightCommitment: "confirmed", + maxRetries: 3, + }); + + // Wait for confirmation using the latest strategy + const latestBlockhash = await agent.connection.getLatestBlockhash(); + await agent.connection.confirmTransaction({ + signature, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }); + + return signature; + } catch (error: any) { + throw new Error(`Lending failed: ${error.message}`); + } +}