diff --git a/examples/persistance-agent/index.ts b/examples/persistance-agent/index.ts index 21badd0..9ea1931 100644 --- a/examples/persistance-agent/index.ts +++ b/examples/persistance-agent/index.ts @@ -8,27 +8,30 @@ import * as readline from "readline"; import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres"; dotenv.config(); -const checkpointer = PostgresSaver.fromConnString( - process.env.POSTGRES_DB_URL! -); +const checkpointer = PostgresSaver.fromConnString(process.env.POSTGRES_DB_URL!); function validateEnvironment(): void { - const missingVars: string[] = []; - const requiredVars = ["OPENAI_API_KEY", "RPC_URL", "SOLANA_PRIVATE_KEY", "POSTGRES_DB_URL"]; + const missingVars: string[] = []; + const requiredVars = [ + "OPENAI_API_KEY", + "RPC_URL", + "SOLANA_PRIVATE_KEY", + "POSTGRES_DB_URL", + ]; - requiredVars.forEach((varName) => { - if (!process.env[varName]) { - missingVars.push(varName); - } - }); - - if (missingVars.length > 0) { - console.error("Error: Required environment variables are not set"); - missingVars.forEach((varName) => { - console.error(`${varName}=your_${varName.toLowerCase()}_here`); - }); - process.exit(1); + requiredVars.forEach((varName) => { + if (!process.env[varName]) { + missingVars.push(varName); } + }); + + if (missingVars.length > 0) { + console.error("Error: Required environment variables are not set"); + missingVars.forEach((varName) => { + console.error(`${varName}=your_${varName.toLowerCase()}_here`); + }); + process.exit(1); + } } validateEnvironment(); @@ -36,37 +39,37 @@ validateEnvironment(); const WALLET_DATA_FILE = "wallet_data.txt"; async function initializeAgent() { - try { - const llm = new ChatOpenAI({ - modelName: "gpt-4o-mini", - temperature: 0.7, - }); + try { + const llm = new ChatOpenAI({ + modelName: "gpt-4o-mini", + temperature: 0.7, + }); - let walletDataStr: string | null = null; + let walletDataStr: string | null = null; - if (fs.existsSync(WALLET_DATA_FILE)) { - try { - walletDataStr = fs.readFileSync(WALLET_DATA_FILE, "utf8"); - } catch (error) { - console.error("Error reading wallet data:", error); - } - } + if (fs.existsSync(WALLET_DATA_FILE)) { + try { + walletDataStr = fs.readFileSync(WALLET_DATA_FILE, "utf8"); + } catch (error) { + console.error("Error reading wallet data:", error); + } + } - const solanaAgent = new SolanaAgentKit( - process.env.SOLANA_PRIVATE_KEY!, - process.env.RPC_URL, - process.env.OPENAI_API_KEY!, - ); + const solanaAgent = new SolanaAgentKit( + process.env.SOLANA_PRIVATE_KEY!, + process.env.RPC_URL, + process.env.OPENAI_API_KEY!, + ); - const tools = createSolanaTools(solanaAgent); - await checkpointer.setup(); - const config = { configurable: { thread_id: "Solana Agent Kit!" } }; + const tools = createSolanaTools(solanaAgent); + await checkpointer.setup(); + const config = { configurable: { thread_id: "Solana Agent Kit!" } }; - const agent = createReactAgent({ - llm, - tools, - checkpointSaver: checkpointer, - messageModifier: ` + const agent = createReactAgent({ + llm, + tools, + checkpointSaver: checkpointer, + messageModifier: ` You are a helpful agent that can interact onchain using the Solana Agent Kit. You are empowered to interact onchain using your tools. If you ever need funds, you can request them from the faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX @@ -75,146 +78,146 @@ async function initializeAgent() { themselves using the Solana Agent Kit, recommend they go to https://www.solanaagentkit.xyz for more information. Be concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested. `, - }); + }); - if (walletDataStr) { - fs.writeFileSync(WALLET_DATA_FILE, walletDataStr); - } - - return { agent, config }; - } catch (error) { - console.error("Failed to initialize agent:", error); - throw error; + if (walletDataStr) { + fs.writeFileSync(WALLET_DATA_FILE, walletDataStr); } + + return { agent, config }; + } catch (error) { + console.error("Failed to initialize agent:", error); + throw error; + } } async function runAutonomousMode(agent: any, config: any, interval = 10) { - console.log("Starting autonomous mode..."); + console.log("Starting autonomous mode..."); - while (true) { - try { - const thought = - "Be creative and do something interesting on the blockchain. " + - "Choose an action or set of actions and execute it that highlights your abilities."; + while (true) { + try { + const thought = + "Be creative and do something interesting on the blockchain. " + + "Choose an action or set of actions and execute it that highlights your abilities."; - const stream = await agent.stream( - { messages: [new HumanMessage(thought)] }, - config, - ); + const stream = await agent.stream( + { messages: [new HumanMessage(thought)] }, + config, + ); - for await (const chunk of stream) { - if ("agent" in chunk) { - console.log(chunk.agent.messages[0].content); - } else if ("tools" in chunk) { - console.log(chunk.tools.messages[0].content); - } - console.log("-------------------"); - } - - await new Promise((resolve) => setTimeout(resolve, interval * 1000)); - } catch (error) { - if (error instanceof Error) { - console.error("Error:", error.message); - } - process.exit(1); + for await (const chunk of stream) { + if ("agent" in chunk) { + console.log(chunk.agent.messages[0].content); + } else if ("tools" in chunk) { + console.log(chunk.tools.messages[0].content); } + console.log("-------------------"); + } + + await new Promise((resolve) => setTimeout(resolve, interval * 1000)); + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); } + } } async function runChatMode(agent: any, config: any) { - console.log("Starting chat mode... Type 'exit' to end."); + console.log("Starting chat mode... Type 'exit' to end."); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); - const question = (prompt: string): Promise => - new Promise((resolve) => rl.question(prompt, resolve)); + const question = (prompt: string): Promise => + new Promise((resolve) => rl.question(prompt, resolve)); - try { - while (true) { - const userInput = await question("\nPrompt: "); + try { + while (true) { + const userInput = await question("\nPrompt: "); - if (userInput.toLowerCase() === "exit") { - break; - } + if (userInput.toLowerCase() === "exit") { + break; + } - const stream = await agent.stream( - { messages: [new HumanMessage(userInput)] }, - config, - ); + const stream = await agent.stream( + { messages: [new HumanMessage(userInput)] }, + config, + ); - for await (const chunk of stream) { - if ("agent" in chunk) { - console.log(chunk.agent.messages[0].content); - } else if ("tools" in chunk) { - console.log(chunk.tools.messages[0].content); - } - console.log("-------------------"); - } + for await (const chunk of stream) { + if ("agent" in chunk) { + console.log(chunk.agent.messages[0].content); + } else if ("tools" in chunk) { + console.log(chunk.tools.messages[0].content); } - } catch (error) { - if (error instanceof Error) { - console.error("Error:", error.message); - } - process.exit(1); - } finally { - rl.close(); + console.log("-------------------"); + } } + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } finally { + rl.close(); + } } async function chooseMode(): Promise<"chat" | "auto"> { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); - const question = (prompt: string): Promise => - new Promise((resolve) => rl.question(prompt, resolve)); + const question = (prompt: string): Promise => + new Promise((resolve) => rl.question(prompt, resolve)); - while (true) { - console.log("\nAvailable modes:"); - console.log("1. chat - Interactive chat mode"); - console.log("2. auto - Autonomous action mode"); + while (true) { + console.log("\nAvailable modes:"); + console.log("1. chat - Interactive chat mode"); + console.log("2. auto - Autonomous action mode"); - const choice = (await question("\nChoose a mode (enter number or name): ")) - .toLowerCase() - .trim(); + const choice = (await question("\nChoose a mode (enter number or name): ")) + .toLowerCase() + .trim(); - rl.close(); + rl.close(); - if (choice === "1" || choice === "chat") { - return "chat"; - } else if (choice === "2" || choice === "auto") { - return "auto"; - } - console.log("Invalid choice. Please try again."); + if (choice === "1" || choice === "chat") { + return "chat"; + } else if (choice === "2" || choice === "auto") { + return "auto"; } + console.log("Invalid choice. Please try again."); + } } async function main() { - try { - console.log("Starting Agent..."); - const { agent, config } = await initializeAgent(); - const mode = await chooseMode(); + try { + console.log("Starting Agent..."); + const { agent, config } = await initializeAgent(); + const mode = await chooseMode(); - if (mode === "chat") { - await runChatMode(agent, config); - } else { - await runAutonomousMode(agent, config); - } - } catch (error) { - if (error instanceof Error) { - console.error("Error:", error.message); - } - process.exit(1); + if (mode === "chat") { + await runChatMode(agent, config); + } else { + await runAutonomousMode(agent, config); } + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } } if (require.main === module) { - main().catch((error) => { - console.error("Fatal error:", error); - process.exit(1); - }); + main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); + }); } diff --git a/src/agent/index.ts b/src/agent/index.ts index 0433374..9fe2c17 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -356,21 +356,15 @@ export class SolanaAgentKit { async tensorListNFT( nftMint: PublicKey, price: number, - expirySeconds?: number ): Promise { - return listNFTForSale(this, nftMint, price, expirySeconds); + return listNFTForSale(this, nftMint, price); } - async tensorBuyNFT( - nftMint: PublicKey, - maxPrice: number - ): Promise { + async tensorBuyNFT(nftMint: PublicKey, maxPrice: number): Promise { return buyNFT(this, nftMint, maxPrice); } - async tensorCancelListing( - nftMint: PublicKey - ): Promise { + async tensorCancelListing(nftMint: PublicKey): Promise { return cancelListing(this, nftMint); } } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index ddf28c9..27e5cc1 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -1332,8 +1332,7 @@ export class SolanaListNFTForSaleTool extends Tool { Inputs (input is a JSON string): nftMint: string, the mint address of the NFT (required) - price: number, price in SOL (required) - expirySeconds: number, expiry time in seconds (optional)`; + price: number, price in SOL (required)`; constructor(private solanaKit: SolanaAgentKit) { super(); @@ -1342,25 +1341,26 @@ export class SolanaListNFTForSaleTool extends Tool { protected async _call(input: string): Promise { try { const parsedInput = JSON.parse(input); - + // Validate NFT ownership first - const nftAccount = await this.solanaKit.connection.getTokenAccountsByOwner( - this.solanaKit.wallet_address, - { mint: new PublicKey(parsedInput.nftMint) } - ); - + const nftAccount = + await this.solanaKit.connection.getTokenAccountsByOwner( + this.solanaKit.wallet_address, + { mint: new PublicKey(parsedInput.nftMint) }, + ); + if (nftAccount.value.length === 0) { return JSON.stringify({ status: "error", - message: "NFT not found in wallet. Please make sure you own this NFT.", - code: "NFT_NOT_FOUND" + message: + "NFT not found in wallet. Please make sure you own this NFT.", + code: "NFT_NOT_FOUND", }); } const tx = await this.solanaKit.tensorListNFT( new PublicKey(parsedInput.nftMint), parsedInput.price, - parsedInput.expirySeconds ); return JSON.stringify({ @@ -1368,13 +1368,13 @@ export class SolanaListNFTForSaleTool extends Tool { message: "NFT listed for sale successfully", transaction: tx, price: parsedInput.price, - nftMint: parsedInput.nftMint + nftMint: parsedInput.nftMint, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -1395,10 +1395,10 @@ export class SolanaBuyNFTTool extends Tool { protected async _call(input: string): Promise { try { const parsedInput = JSON.parse(input); - + const tx = await this.solanaKit.tensorBuyNFT( new PublicKey(parsedInput.nftMint), - parsedInput.maxPrice + parsedInput.maxPrice, ); return JSON.stringify({ @@ -1406,13 +1406,13 @@ export class SolanaBuyNFTTool extends Tool { message: "NFT purchased successfully", transaction: tx, maxPrice: parsedInput.maxPrice, - nftMint: parsedInput.nftMint + nftMint: parsedInput.nftMint, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -1432,22 +1432,22 @@ export class SolanaCancelNFTListingTool extends Tool { protected async _call(input: string): Promise { try { const parsedInput = JSON.parse(input); - + const tx = await this.solanaKit.tensorCancelListing( - new PublicKey(parsedInput.nftMint) + new PublicKey(parsedInput.nftMint), ); return JSON.stringify({ status: "success", message: "NFT listing cancelled successfully", transaction: tx, - nftMint: parsedInput.nftMint + nftMint: parsedInput.nftMint, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } diff --git a/src/tools/tensor_trade.ts b/src/tools/tensor_trade.ts index d478fe1..030536f 100644 --- a/src/tools/tensor_trade.ts +++ b/src/tools/tensor_trade.ts @@ -3,17 +3,20 @@ import { TensorSwapSDK } from "@tensor-oss/tensorswap-sdk"; import { PublicKey, Transaction } from "@solana/web3.js"; import { AnchorProvider, Wallet } from "@coral-xyz/anchor"; import { BN } from "bn.js"; -import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID,getAccount } from '@solana/spl-token'; +import { + getAssociatedTokenAddress, + TOKEN_PROGRAM_ID, + getAccount, +} from "@solana/spl-token"; export async function listNFTForSale( agent: SolanaAgentKit, nftMint: PublicKey, price: number, - expirySeconds?: number ): Promise { try { if (!PublicKey.isOnCurve(nftMint)) { - throw new Error('Invalid NFT mint address'); + throw new Error("Invalid NFT mint address"); } const mintInfo = await agent.connection.getAccountInfo(nftMint); @@ -21,33 +24,32 @@ export async function listNFTForSale( throw new Error(`NFT mint ${nftMint.toString()} does not exist`); } - const ata = await getAssociatedTokenAddress( - nftMint, - agent.wallet_address - ); + const ata = await getAssociatedTokenAddress(nftMint, agent.wallet_address); try { - const tokenAccount = await getAccount( - agent.connection, - ata - ); - + const tokenAccount = await getAccount(agent.connection, ata); + if (!tokenAccount || tokenAccount.amount <= 0) { throw new Error(`You don't own this NFT (${nftMint.toString()})`); } } catch (e) { - throw new Error(`No token account found for mint ${nftMint.toString()}. Make sure you own this NFT.`); + throw new Error( + `No token account found for mint ${nftMint.toString()}. Make sure you own this NFT.`, + ); } const provider = new AnchorProvider( agent.connection, new Wallet(agent.wallet), - AnchorProvider.defaultOptions() + AnchorProvider.defaultOptions(), ); - + const tensorSwapSdk = new TensorSwapSDK({ provider }); const priceInLamports = new BN(price * 1e9); - const nftSource = await getAssociatedTokenAddress(nftMint, agent.wallet_address); + const nftSource = await getAssociatedTokenAddress( + nftMint, + agent.wallet_address, + ); const { tx } = await tensorSwapSdk.list({ nftMint, @@ -55,12 +57,15 @@ export async function listNFTForSale( owner: agent.wallet_address, price: priceInLamports, tokenProgram: TOKEN_PROGRAM_ID, - payer: agent.wallet_address + payer: agent.wallet_address, }); const transaction = new Transaction(); transaction.add(...tx.ixs); - return await agent.connection.sendTransaction(transaction, [agent.wallet, ...tx.extraSigners]); + return await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ...tx.extraSigners, + ]); } catch (error: any) { console.error("Full error details:", error); throw error; @@ -70,17 +75,20 @@ export async function listNFTForSale( export async function buyNFT( agent: SolanaAgentKit, nftMint: PublicKey, - maxPrice: number + maxPrice: number, ): Promise { const provider = new AnchorProvider( agent.connection, new Wallet(agent.wallet), - AnchorProvider.defaultOptions() + AnchorProvider.defaultOptions(), ); - + const tensorSwapSdk = new TensorSwapSDK({ provider }); const maxPriceInLamports = new BN(maxPrice * 1e9); - const nftBuyerAcc = await getAssociatedTokenAddress(nftMint, agent.wallet_address); + const nftBuyerAcc = await getAssociatedTokenAddress( + nftMint, + agent.wallet_address, + ); const { tx } = await tensorSwapSdk.buySingleListingT22({ nftMint, @@ -91,42 +99,48 @@ export async function buyNFT( takerBroker: null, compute: null, priorityMicroLamports: null, - transferHook: null + transferHook: null, }); const transaction = new Transaction(); transaction.add(...tx.ixs); - return await agent.connection.sendTransaction(transaction, [agent.wallet, ...tx.extraSigners]); + return await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ...tx.extraSigners, + ]); } export async function cancelListing( agent: SolanaAgentKit, - nftMint: PublicKey + nftMint: PublicKey, ): Promise { const provider = new AnchorProvider( agent.connection, new Wallet(agent.wallet), - AnchorProvider.defaultOptions() + AnchorProvider.defaultOptions(), ); - + const tensorSwapSdk = new TensorSwapSDK({ provider }); const nftDest = await getAssociatedTokenAddress( - nftMint, + nftMint, agent.wallet_address, false, - TOKEN_PROGRAM_ID + TOKEN_PROGRAM_ID, ); - + const { tx } = await tensorSwapSdk.delist({ nftMint, nftDest, owner: agent.wallet_address, tokenProgram: TOKEN_PROGRAM_ID, payer: agent.wallet_address, - authData: null + authData: null, }); const transaction = new Transaction(); transaction.add(...tx.ixs); - return await agent.connection.sendTransaction(transaction, [agent.wallet, ...tx.extraSigners]); -} \ No newline at end of file + return await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ...tx.extraSigners, + ]); +}