diff --git a/package.json b/package.json index 4d0e5d8..97600fb 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "scripts": { "build": "tsc", "docs": "typedoc src --out docs", - "test": "ts-node test/index.ts" + "test": "ts-node test/index.ts", + "generate": "ts-node src/utils/keypair.ts" }, "keywords": [], "author": "", @@ -16,6 +17,7 @@ "@bonfida/spl-name-service": "^3.0.7", "@langchain/core": "^0.3.18", "@langchain/groq": "^0.1.2", + "@langchain/langgraph": "^0.2.27", "@langchain/openai": "^0.3.13", "@metaplex-foundation/mpl-core": "^1.1.1", "@metaplex-foundation/mpl-token-metadata": "^3.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 445564c..a019726 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@langchain/groq': specifier: ^0.1.2 version: 0.1.2(@langchain/core@0.3.18(openai@4.75.0(zod@3.23.8))) + '@langchain/langgraph': + specifier: ^0.2.27 + version: 0.2.27(@langchain/core@0.3.18(openai@4.75.0(zod@3.23.8))) '@langchain/openai': specifier: ^0.3.13 version: 0.3.13(@langchain/core@0.3.18(openai@4.75.0(zod@3.23.8))) @@ -107,6 +110,21 @@ packages: peerDependencies: '@langchain/core': '>=0.2.21 <0.4.0' + '@langchain/langgraph-checkpoint@0.0.13': + resolution: {integrity: sha512-amdmBcNT8a9xP2VwcEWxqArng4gtRDcnVyVI4DsQIo1Aaz8e8+hH17zSwrUF3pt1pIYztngIfYnBOim31mtKMg==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.31 <0.4.0' + + '@langchain/langgraph-sdk@0.0.31': + resolution: {integrity: sha512-oYZWoC3x7vH9bAL1Y30XjtuWnic1j3knXD4BbldsY0chFLxwIT5i6/GMThNy3Oiwb4SB+c6gvaSuxBNDkp7dkw==} + + '@langchain/langgraph@0.2.27': + resolution: {integrity: sha512-7+PlVXlNpswzXzZp/k8O99YBN3zBkUdusfyxISkZ/gdXz1p5RySQEpKQ4EVIZnzBrZ98zZ3FArj4OWOgeF0EeA==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.36 <0.3.0 || >=0.3.9 < 0.4.0' + '@langchain/openai@0.3.13': resolution: {integrity: sha512-lfiauYttb1Vv1GVGDNZlse8475RUsKm9JJ7X9kMVtYoOQnK8xxzMVSrpW7HYLmJokrtVgF6STwRzNJI2gZ3uBw==} engines: {node: '>=18'} @@ -368,6 +386,9 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -969,6 +990,10 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -1107,6 +1132,26 @@ snapshots: transitivePeerDependencies: - encoding + '@langchain/langgraph-checkpoint@0.0.13(@langchain/core@0.3.18(openai@4.75.0(zod@3.23.8)))': + dependencies: + '@langchain/core': 0.3.18(openai@4.75.0(zod@3.23.8)) + uuid: 10.0.0 + + '@langchain/langgraph-sdk@0.0.31': + dependencies: + '@types/json-schema': 7.0.15 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 9.0.1 + + '@langchain/langgraph@0.2.27(@langchain/core@0.3.18(openai@4.75.0(zod@3.23.8)))': + dependencies: + '@langchain/core': 0.3.18(openai@4.75.0(zod@3.23.8)) + '@langchain/langgraph-checkpoint': 0.0.13(@langchain/core@0.3.18(openai@4.75.0(zod@3.23.8))) + '@langchain/langgraph-sdk': 0.0.31 + uuid: 10.0.0 + zod: 3.23.8 + '@langchain/openai@0.3.13(@langchain/core@0.3.18(openai@4.75.0(zod@3.23.8)))': dependencies: '@langchain/core': 0.3.18(openai@4.75.0(zod@3.23.8)) @@ -1478,6 +1523,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -2059,6 +2106,8 @@ snapshots: uuid@8.3.2: {} + uuid@9.0.1: {} + v8-compile-cache-lib@3.0.1: {} vfile-message@4.0.2: diff --git a/src/langchain/index.ts b/src/langchain/index.ts index a0615c6..e987552 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -4,6 +4,7 @@ import { PublicKey } from "@solana/web3.js"; import { PumpFunTokenOptions } from "../types"; import { toJSON } from "../utils/toJSON"; import { create_image } from "../tools/create_image"; + export class SolanaBalanceTool extends Tool { name = "solana_balance"; description = @@ -17,17 +18,17 @@ export class SolanaBalanceTool extends Tool { try { const tokenAddress = input ? new PublicKey(input) : undefined; const balance = await this.solanaKit.getBalance(tokenAddress); - + return JSON.stringify({ status: "success", balance: balance, - token: input || "SOL" + token: input || "SOL", }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -60,22 +61,24 @@ export class SolanaTransferTool extends Tool { this.validateInput(parsedInput); const recipient = new PublicKey(parsedInput.to); - const mintAddress = parsedInput.mint ? new PublicKey(parsedInput.mint) : undefined; + const mintAddress = parsedInput.mint + ? new PublicKey(parsedInput.mint) + : undefined; await this.solanaKit.transfer(recipient, parsedInput.amount, mintAddress); - + return JSON.stringify({ status: "success", message: "Transfer completed successfully", amount: parsedInput.amount, recipient: parsedInput.to, - token: parsedInput.mint || "SOL" + token: parsedInput.mint || "SOL", }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -91,12 +94,20 @@ export class SolanaDeployTokenTool extends Tool { } private validateInput(input: any): void { - if (input.decimals !== undefined && - (typeof input.decimals !== "number" || input.decimals < 0 || input.decimals > 9)) { - throw new Error("decimals must be a number between 0 and 9 when provided"); + if ( + input.decimals !== undefined && + (typeof input.decimals !== "number" || + input.decimals < 0 || + input.decimals > 9) + ) { + throw new Error( + "decimals must be a number between 0 and 9 when provided" + ); } - if (input.initialSupply !== undefined && - (typeof input.initialSupply !== "number" || input.initialSupply <= 0)) { + if ( + input.initialSupply !== undefined && + (typeof input.initialSupply !== "number" || input.initialSupply <= 0) + ) { throw new Error("initialSupply must be a positive number when provided"); } } @@ -107,18 +118,18 @@ export class SolanaDeployTokenTool extends Tool { this.validateInput(parsedInput); const result = await this.solanaKit.deployToken(parsedInput.decimals); - + return JSON.stringify({ status: "success", message: "Token deployed successfully", mintAddress: result.mint.toString(), - decimals: parsedInput.decimals || 9 + decimals: parsedInput.decimals || 9, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -140,11 +151,15 @@ export class SolanaDeployCollectionTool extends Tool { if (!input.uri || typeof input.uri !== "string") { throw new Error("uri is required and must be a string"); } - if (input.royaltyBasisPoints !== undefined && - (typeof input.royaltyBasisPoints !== "number" || - input.royaltyBasisPoints < 0 || - input.royaltyBasisPoints > 10000)) { - throw new Error("royaltyBasisPoints must be a number between 0 and 10000 when provided"); + if ( + input.royaltyBasisPoints !== undefined && + (typeof input.royaltyBasisPoints !== "number" || + input.royaltyBasisPoints < 0 || + input.royaltyBasisPoints > 10000) + ) { + throw new Error( + "royaltyBasisPoints must be a number between 0 and 10000 when provided" + ); } if (input.creators) { if (!Array.isArray(input.creators)) { @@ -152,10 +167,18 @@ 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`); + throw new Error( + `creator[${index}].address is required and must be a string` + ); } - if (typeof creator.percentage !== "number" || creator.percentage < 0 || creator.percentage > 100) { - throw new Error(`creator[${index}].percentage must be a number between 0 and 100`); + if ( + typeof creator.percentage !== "number" || + creator.percentage < 0 || + creator.percentage > 100 + ) { + throw new Error( + `creator[${index}].percentage must be a number between 0 and 100` + ); } }); } @@ -167,18 +190,18 @@ export class SolanaDeployCollectionTool extends Tool { this.validateInput(parsedInput); const result = await this.solanaKit.deployCollection(parsedInput); - + return JSON.stringify({ status: "success", message: "Collection deployed successfully", collectionAddress: result.collectionAddress.toString(), - name: parsedInput.name + name: parsedInput.name, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -224,19 +247,19 @@ export class SolanaMintNFTTool extends Tool { parsedInput.metadata, parsedInput.recipient ? new PublicKey(parsedInput.recipient) : undefined ); - + return JSON.stringify({ status: "success", message: "NFT minted successfully", mintAddress: result.mint.toString(), name: parsedInput.metadata.name, - recipient: parsedInput.recipient || result.mint.toString() + recipient: parsedInput.recipient || result.mint.toString(), }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -261,11 +284,15 @@ export class SolanaTradeTool extends Tool { if (input.inputMint !== undefined && typeof input.inputMint !== "string") { throw new Error("inputMint must be a string when provided"); } - if (input.slippageBps !== undefined && - (typeof input.slippageBps !== "number" || - input.slippageBps < 0 || - input.slippageBps > 10000)) { - throw new Error("slippageBps must be a number between 0 and 10000 when provided"); + if ( + input.slippageBps !== undefined && + (typeof input.slippageBps !== "number" || + input.slippageBps < 0 || + input.slippageBps > 10000) + ) { + throw new Error( + "slippageBps must be a number between 0 and 10000 when provided" + ); } } @@ -277,23 +304,25 @@ export class SolanaTradeTool extends Tool { const tx = await this.solanaKit.trade( new PublicKey(parsedInput.outputMint), parsedInput.inputAmount, - parsedInput.inputMint ? new PublicKey(parsedInput.inputMint) : undefined, + parsedInput.inputMint + ? new PublicKey(parsedInput.inputMint) + : undefined, parsedInput.slippageBps ); - + return JSON.stringify({ status: "success", message: "Trade executed successfully", transaction: tx, inputAmount: parsedInput.inputAmount, inputToken: parsedInput.inputMint || "SOL", - outputToken: parsedInput.outputMint + outputToken: parsedInput.outputMint, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -310,17 +339,17 @@ export class SolanaRequestFundsTool extends Tool { protected async _call(_input: string): Promise { try { await this.solanaKit.requestFaucetFunds(); - + return JSON.stringify({ status: "success", message: "Successfully requested faucet funds", - network: this.solanaKit.connection.rpcEndpoint.split("/")[2] + network: this.solanaKit.connection.rpcEndpoint.split("/")[2], }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -339,8 +368,10 @@ export class SolanaRegisterDomainTool extends Tool { if (!input.name || typeof input.name !== "string") { throw new Error("name is required and must be a string"); } - if (input.spaceKB !== undefined && - (typeof input.spaceKB !== "number" || input.spaceKB <= 0)) { + if ( + input.spaceKB !== undefined && + (typeof input.spaceKB !== "number" || input.spaceKB <= 0) + ) { throw new Error("spaceKB must be a positive number when provided"); } } @@ -351,22 +382,22 @@ export class SolanaRegisterDomainTool extends Tool { this.validateInput(parsedInput); const tx = await this.solanaKit.registerDomain( - parsedInput.name, + parsedInput.name, parsedInput.spaceKB || 1 ); - + return JSON.stringify({ status: "success", message: "Domain registered successfully", transaction: tx, domain: `${parsedInput.name}.sol`, - spaceKB: parsedInput.spaceKB || 1 + spaceKB: parsedInput.spaceKB || 1, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } } @@ -387,14 +418,26 @@ export class SolanaGetWalletAddressTool extends Tool { export class SolanaPumpfunTokenLaunchTool extends Tool { name = "solana_launch_pumpfun_token"; - description = - "Launch a new token on Pump.fun via Solana Agent Kit. Requires a JSON input with tokenName, tokenTicker, description, imageUrl, and optional fields for twitter, telegram, website, and initialLiquiditySOL."; + // description = + // "Launch a new token on Pump.fun via Solana Agent Kit. Requires a JSON input with tokenName, tokenTicker, description, imageUrl, and optional fields for twitter, telegram, website, and initialLiquiditySOL."; + + 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. + For generating the image, you can use the solana_create_image tool. + + Inputs: + tokenName: string, eg "PumpFun Token", + tokenTicker: string, eg "PUMP", + description: string, eg "PumpFun Token is a token on the Solana blockchain", + imageUrl: string, eg "https://i.imgur.com/UFm07Np_d.png`; constructor(private solanaKit: SolanaAgentKit) { super(); } private validateInput(input: any): void { + console.log(input); if (!input.tokenName || typeof input.tokenName !== "string") { throw new Error("tokenName is required and must be a string"); } @@ -418,11 +461,10 @@ export class SolanaPumpfunTokenLaunchTool extends Tool { protected async _call(input: string): Promise { try { // Parse and normalize input - const parsedInput = toJSON(input); - // Validate the input - this.validateInput(parsedInput); + input = input.trim(); + let parsedInput = JSON.parse(input); - console.log(parsedInput); + this.validateInput(parsedInput); // Launch token with validated input await this.solanaKit.launchPumpFunToken( @@ -473,17 +515,17 @@ export class SolanaCreateImageTool extends Tool { try { this.validateInput(input); const result = await create_image(this.solanaKit, input.trim()); - + return JSON.stringify({ status: "success", message: "Image created successfully", - ...result + ...result, }); } catch (error: any) { return JSON.stringify({ status: "error", message: error.message, - code: error.code || "UNKNOWN_ERROR" + code: error.code || "UNKNOWN_ERROR", }); } }