From 91f7b8a4787d8e784876e08c04a037bdec2fa13e Mon Sep 17 00:00:00 2001 From: Arihant Bansal <17180950+arihantbansal@users.noreply.github.com> Date: Thu, 12 Dec 2024 01:26:12 +0530 Subject: [PATCH] feat: add staking --- README.md | 15 ++++++++ src/agent/index.ts | 7 ++++ src/langchain/index.ts | 71 ++++++++++++++++++++++++++++++------- src/tools/index.ts | 1 + src/tools/stake_with_jup.ts | 39 ++++++++++++++++++++ 5 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 src/tools/stake_with_jup.ts diff --git a/README.md b/README.md index 2dd01d8..d098bf6 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A powerful toolkit for interacting with the Solana blockchain, providing easy-to - Deploy new SPL tokens - Transfer SOL and SPL tokens - Check token balances + - Stake SOL - 🖼️ NFT Management - Deploy NFT collections @@ -110,6 +111,17 @@ const signature = await lendAsset( ); ``` +### Stake SOL + +```typescript +import { stakeWithJup } from 'solana-agent-kit'; + +const signature = await stakeWithJup( + agent, + 1 // amount in SOL +); +``` + ## API Reference ### Core Functions @@ -135,6 +147,9 @@ Check SOL or token balance for the agent's wallet. #### `lendAsset(agent, assetMint, amount, apiKey)` Lend idle assets to earn interest with Lulo. +#### `stakeWithJup(agent, amount)` +Stake SOL with Jupiter to earn rewards. + ## Dependencies The toolkit relies on several key Solana and Metaplex libraries: diff --git a/src/agent/index.ts b/src/agent/index.ts index 483763d..b7c0ade 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -10,6 +10,7 @@ import { trade, registerDomain, launchPumpFunToken, + stakeWithJup, } from "../tools"; import { CollectionOptions, PumpFunTokenOptions } from "../types"; import { DEFAULT_OPTIONS } from "../constants"; @@ -101,4 +102,10 @@ export class SolanaAgentKit { options ); } + + async stake( + amount: number, + ) { + return stakeWithJup(this, amount); + } } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index da063c1..4f1f876 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -42,7 +42,7 @@ export class SolanaTransferTool extends Tool { name = "solana_transfer"; description = `Transfer tokens or SOL to another address ( also called as wallet address ). - Inputs ( input is a JSON string ): + Inputs ( input is a JSON string ): to: string, eg "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk" (required) amount: number, eg 1 (required) mint?: string, eg "So11111111111111111111111111111111111111112" or "SENDdRQtYMWaQrBroBrJ2Q53fgVuq95CV9UPGEvpCxa" (optional)`; @@ -64,7 +64,7 @@ export class SolanaTransferTool extends Tool { const tx = await this.solanaKit.transfer( recipient, parsedInput.amount, - mintAddress + mintAddress, ); return JSON.stringify({ @@ -102,7 +102,7 @@ export class SolanaDeployTokenTool extends Tool { input.decimals > 9) ) { throw new Error( - "decimals must be a number between 0 and 9 when provided" + "decimals must be a number between 0 and 9 when provided", ); } if ( @@ -159,7 +159,7 @@ export class SolanaDeployCollectionTool extends Tool { input.royaltyBasisPoints > 10000) ) { throw new Error( - "royaltyBasisPoints must be a number between 0 and 10000 when provided" + "royaltyBasisPoints must be a number between 0 and 10000 when provided", ); } if (input.creators) { @@ -169,7 +169,7 @@ export class SolanaDeployCollectionTool extends Tool { input.creators.forEach((creator: any, index: number) => { if (!creator.address || typeof creator.address !== "string") { throw new Error( - `creator[${index}].address is required and must be a string` + `creator[${index}].address is required and must be a string`, ); } if ( @@ -178,7 +178,7 @@ export class SolanaDeployCollectionTool extends Tool { creator.percentage > 100 ) { throw new Error( - `creator[${index}].percentage must be a number between 0 and 100` + `creator[${index}].percentage must be a number between 0 and 100`, ); } }); @@ -246,7 +246,9 @@ export class SolanaMintNFTTool extends Tool { const result = await this.solanaKit.mintNFT( new PublicKey(parsedInput.collectionMint), parsedInput.metadata, - parsedInput.recipient ? new PublicKey(parsedInput.recipient) : undefined + parsedInput.recipient + ? new PublicKey(parsedInput.recipient) + : undefined, ); return JSON.stringify({ @@ -290,7 +292,7 @@ export class SolanaTradeTool extends Tool { parsedInput.inputMint ? new PublicKey(parsedInput.inputMint) : new PublicKey("So11111111111111111111111111111111111111112"), - parsedInput.slippageBps + parsedInput.slippageBps, ); return JSON.stringify({ @@ -371,7 +373,7 @@ export class SolanaRegisterDomainTool extends Tool { const tx = await this.solanaKit.registerDomain( parsedInput.name, - parsedInput.spaceKB || 1 + parsedInput.spaceKB || 1, ); return JSON.stringify({ @@ -409,9 +411,9 @@ export class SolanaPumpfunTokenLaunchTool extends Tool { description = `This tool can be used to launch a token on Pump.fun, do not use this tool for any other purpose, or for creating SPL tokens. - If the user asks you to chose the parameters, you should generate valid values. + If the user asks you to chose the parameters, you should generate valid values. For generating the image, you can use the solana_create_image tool. - + Inputs: tokenName: string, eg "PumpFun Token", tokenTicker: string, eg "PUMP", @@ -463,7 +465,7 @@ export class SolanaPumpfunTokenLaunchTool extends Tool { telegram: parsedInput.telegram, website: parsedInput.website, initialLiquiditySOL: parsedInput.initialLiquiditySOL, - } + }, ); return JSON.stringify({ @@ -517,6 +519,50 @@ export class SolanaCreateImageTool extends Tool { } } +export class SolanaStakeTool extends Tool { + name = "solana_stake"; + description = `This tool can be used to stake your SOL (Solana) + + Inputs ( input is a JSON string ): + amount: number, eg 1 or 0.01 (required)`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + private validateInput(input: any): void { + if ( + input.amount !== undefined && + (typeof input.amount !== "number" || input.amount <= 0) + ) { + throw new Error("amount must be a positive number when provided"); + } + } + + protected async _call(input: string): Promise { + try { + const parsedInput = toJSON(input); + this.validateInput(parsedInput); + + const tx = await this.solanaKit.stake(parsedInput.amount); + + return JSON.stringify({ + status: "success", + message: "Staked successfully", + transaction: tx, + amount: parsedInput.amount, + }); + } catch (error: any) { + console.log(error); + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + export function createSolanaTools(solanaKit: SolanaAgentKit) { return [ new SolanaBalanceTool(solanaKit), @@ -530,5 +576,6 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaGetWalletAddressTool(solanaKit), new SolanaPumpfunTokenLaunchTool(solanaKit), new SolanaCreateImageTool(solanaKit), + new SolanaStakeTool(solanaKit), ]; } diff --git a/src/tools/index.ts b/src/tools/index.ts index b559ab4..5eb38ca 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -7,3 +7,4 @@ export * from './transfer'; export * from './trade'; export * from './register_domain'; export * from './launch_pumpfun_token'; +export * from './stake_with_jup'; \ No newline at end of file diff --git a/src/tools/stake_with_jup.ts b/src/tools/stake_with_jup.ts new file mode 100644 index 0000000..17c153c --- /dev/null +++ b/src/tools/stake_with_jup.ts @@ -0,0 +1,39 @@ +import { VersionedTransaction } from "@solana/web3.js"; +import { SolanaAgentKit } from "../agent"; + +/** + * Stake SOL with Jup validator + * @param agent SolanaAgentKit instance + * @param amount Amount of SOL to stake + * @returns Transaction signature + */ +export async function stakeWithJup( + agent: SolanaAgentKit, + amount: number, +): Promise { + try { + const res = await fetch( + `https://worker.jup.ag/blinks/swap/So11111111111111111111111111111111111111112/jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v/${amount}`, + { + method: "POST", + body: JSON.stringify({ + account: agent.wallet.publicKey.toBase58(), + }), + }, + ); + + const data = await res.json(); + + const txn = VersionedTransaction.deserialize( + Buffer.from(data.transaction, "base64"), + ); + + // Sign and send transaction + txn.sign([agent.wallet]); + const signature = await agent.connection.sendTransaction(txn); + return signature; + } catch (error: any) { + console.error(error); + throw new Error(`jupSOL staking failed: ${error.message}`); + } +}