From 205ff4891c1eac3edcbd256d76d89872ed333b14 Mon Sep 17 00:00:00 2001 From: aryan Date: Thu, 5 Dec 2024 20:28:02 +0530 Subject: [PATCH] fix: json --- package.json | 1 + pnpm-lock.yaml | 46 +++++++++++- src/langchain/index.ts | 115 +++++++++++++++++++++--------- src/tools/launch_pumpfun_token.ts | 3 + src/utils/toJSON.ts | 26 +++++++ 5 files changed, 155 insertions(+), 36 deletions(-) create mode 100644 src/utils/toJSON.ts diff --git a/package.json b/package.json index 79c2d57..e2f9c23 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@bonfida/spl-name-service": "^3.0.7", "@langchain/core": "^0.3.18", + "@langchain/groq": "^0.1.2", "@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 0229538..562ff0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@langchain/core': specifier: ^0.3.18 version: 0.3.18(openai@4.72.0(zod@3.23.8)) + '@langchain/groq': + specifier: ^0.1.2 + version: 0.1.2(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8))) '@langchain/openai': specifier: ^0.3.13 version: 0.3.13(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8))) @@ -49,7 +52,7 @@ importers: version: 4.0.1 langchain: specifier: ^0.3.6 - version: 0.3.6(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8)))(axios@1.7.7)(openai@4.72.0(zod@3.23.8)) + version: 0.3.6(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8)))(@langchain/groq@0.1.2(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8))))(axios@1.7.7)(openai@4.72.0(zod@3.23.8)) typedoc: specifier: ^0.26.11 version: 0.26.11(typescript@5.6.3) @@ -95,6 +98,12 @@ packages: resolution: {integrity: sha512-IEZCrFs1Xd0J2FTH1D3Lnm3/Yk2r8LSpwDeLYwcCom3rNAK5k4mKQ2rwIpNq3YuqBdrTNMKRO+PopjkP1SB17A==} engines: {node: '>=18'} + '@langchain/groq@0.1.2': + resolution: {integrity: sha512-bgQ9yGoNHOwG6LG2ngGvSNxF/1U1c1u3vKmFWmzecFIcBoQQOJY0jb0MrL3g1uTife0Sr3zxkWKXQg2aK/U4Sg==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.21 <0.4.0' + '@langchain/openai@0.3.13': resolution: {integrity: sha512-lfiauYttb1Vv1GVGDNZlse8475RUsKm9JJ7X9kMVtYoOQnK8xxzMVSrpW7HYLmJokrtVgF6STwRzNJI2gZ3uBw==} engines: {node: '>=18'} @@ -609,6 +618,9 @@ packages: graphemesplit@2.4.4: resolution: {integrity: sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w==} + groq-sdk@0.5.0: + resolution: {integrity: sha512-RVmhW7qZ+XZoy5fIuSdx/LGQJONpL8MHgZEW7dFwTdgkzStub2XQx6OKv28CHogijdwH41J+Npj/z2jBPu3vmw==} + hast-util-to-html@9.0.3: resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==} @@ -963,6 +975,10 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + web-streams-polyfill@4.0.0-beta.3: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} @@ -1078,6 +1094,16 @@ snapshots: transitivePeerDependencies: - openai + '@langchain/groq@0.1.2(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8)))': + dependencies: + '@langchain/core': 0.3.18(openai@4.72.0(zod@3.23.8)) + '@langchain/openai': 0.3.13(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8))) + groq-sdk: 0.5.0 + zod: 3.23.8 + zod-to-json-schema: 3.23.5(zod@3.23.8) + transitivePeerDependencies: + - encoding + '@langchain/openai@0.3.13(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8)))': dependencies: '@langchain/core': 0.3.18(openai@4.72.0(zod@3.23.8)) @@ -1667,6 +1693,19 @@ snapshots: js-base64: 3.7.7 unicode-trie: 2.0.0 + groq-sdk@0.5.0: + dependencies: + '@types/node': 18.19.64 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + web-streams-polyfill: 3.3.3 + transitivePeerDependencies: + - encoding + hast-util-to-html@9.0.3: dependencies: '@types/hast': 3.0.4 @@ -1733,7 +1772,7 @@ snapshots: jsonpointer@5.0.1: {} - langchain@0.3.6(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8)))(axios@1.7.7)(openai@4.72.0(zod@3.23.8)): + langchain@0.3.6(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8)))(@langchain/groq@0.1.2(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8))))(axios@1.7.7)(openai@4.72.0(zod@3.23.8)): dependencies: '@langchain/core': 0.3.18(openai@4.72.0(zod@3.23.8)) '@langchain/openai': 0.3.13(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8))) @@ -1749,6 +1788,7 @@ snapshots: zod: 3.23.8 zod-to-json-schema: 3.23.5(zod@3.23.8) optionalDependencies: + '@langchain/groq': 0.1.2(@langchain/core@0.3.18(openai@4.72.0(zod@3.23.8))) axios: 1.7.7 transitivePeerDependencies: - encoding @@ -2028,6 +2068,8 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.0.0-beta.3: {} webidl-conversions@3.0.1: {} diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 226abc8..a121394 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -1,12 +1,13 @@ import { Tool } from "langchain/tools"; import { SolanaAgentKit } from "../index"; import { PublicKey } from "@solana/web3.js"; -import { launchPumpFunToken } from "../tools"; - +import { PumpFunTokenOptions } from "../types"; +import { toJSON } from "../utils/toJSON"; export class SolanaBalanceTool extends Tool { name = "solana_balance"; - description = "Get the balance of a Solana wallet or token account. Input can be a token address or empty for SOL balance."; - + description = + "Get the balance of a Solana wallet or token account. Input can be a token address or empty for SOL balance."; + constructor(private solanaKit: SolanaAgentKit) { super(); } @@ -24,8 +25,9 @@ export class SolanaBalanceTool extends Tool { export class SolanaTransferTool extends Tool { name = "solana_transfer"; - description = "Transfer tokens or SOL to another address. Input should be JSON string with: {to: string, amount: number, mint?: string}"; - + description = + "Transfer tokens or SOL to another address. Input should be JSON string with: {to: string, amount: number, mint?: string}"; + constructor(private solanaKit: SolanaAgentKit) { super(); } @@ -35,7 +37,7 @@ export class SolanaTransferTool extends Tool { const { to, amount, mint } = JSON.parse(input); const recipient = new PublicKey(to); const mintAddress = mint ? new PublicKey(mint) : undefined; - + await this.solanaKit.transfer(recipient, amount, mintAddress); return `Successfully transferred ${amount} to ${to}`; } catch (error: any) { @@ -46,15 +48,16 @@ export class SolanaTransferTool extends Tool { export class SolanaDeployTokenTool extends Tool { name = "solana_deploy_token"; - description = "Deploy a new SPL token. Input should be JSON string with: {decimals?: number, initialSupply?: number}"; - + description = + "Deploy a new SPL token. Input should be JSON string with: {decimals?: number, initialSupply?: number}"; + constructor(private solanaKit: SolanaAgentKit) { super(); } async _call(input: string): Promise { try { - const validJson = input + const validJson = input .replace(/([a-zA-Z0-9_]+):/g, '"$1":') // Add quotes around keys .trim(); const { decimals = 9 } = JSON.parse(validJson); @@ -68,8 +71,9 @@ export class SolanaDeployTokenTool extends Tool { export class SolanaDeployCollectionTool extends Tool { name = "solana_deploy_collection"; - description = "Deploy a new NFT collection. Input should be JSON with: {name: string, uri: string, royaltyBasisPoints?: number, creators?: Array<{address: string, percentage: number}>}"; - + description = + "Deploy a new NFT collection. Input should be JSON with: {name: string, uri: string, royaltyBasisPoints?: number, creators?: Array<{address: string, percentage: number}>}"; + constructor(private solanaKit: SolanaAgentKit) { super(); } @@ -87,8 +91,9 @@ export class SolanaDeployCollectionTool extends Tool { export class SolanaMintNFTTool extends Tool { name = "solana_mint_nft"; - description = "Mint a new NFT in a collection. Input should be JSON with: {collectionMint: string, metadata: {name: string, symbol: string, uri: string}, recipient?: string}"; - + description = + "Mint a new NFT in a collection. Input should be JSON with: {collectionMint: string, metadata: {name: string, symbol: string, uri: string}, recipient?: string}"; + constructor(private solanaKit: SolanaAgentKit) { super(); } @@ -111,15 +116,17 @@ export class SolanaMintNFTTool extends Tool { export class SolanaTradeTool extends Tool { name = "solana_trade"; - description = "Swap tokens using Jupiter Exchange. Input should be JSON with: {outputMint: string, inputAmount: number, inputMint?: string, slippageBps?: number}"; - + description = + "Swap tokens using Jupiter Exchange. Input should be JSON with: {outputMint: string, inputAmount: number, inputMint?: string, slippageBps?: number}"; + constructor(private solanaKit: SolanaAgentKit) { super(); } async _call(input: string): Promise { try { - const { outputMint, inputAmount, inputMint, slippageBps } = JSON.parse(input); + const { outputMint, inputAmount, inputMint, slippageBps } = + JSON.parse(input); const tx = await this.solanaKit.trade( new PublicKey(outputMint), inputAmount, @@ -136,7 +143,7 @@ export class SolanaTradeTool extends Tool { export class SolanaRequestFundsTool extends Tool { name = "solana_request_funds"; description = "Request SOL from Solana faucet (devnet/testnet only)"; - + constructor(private solanaKit: SolanaAgentKit) { super(); } @@ -153,8 +160,9 @@ export class SolanaRequestFundsTool extends Tool { export class SolanaRegisterDomainTool extends Tool { name = "solana_register_domain"; - description = "Register a .sol domain name. Input should be JSON with: {name: string, spaceKB?: number}"; - + description = + "Register a .sol domain name. Input should be JSON with: {name: string, spaceKB?: number}"; + constructor(private solanaKit: SolanaAgentKit) { super(); } @@ -173,7 +181,7 @@ export class SolanaRegisterDomainTool extends Tool { export class SolanaGetWalletAddressTool extends Tool { name = "solana_get_wallet_address"; description = "Get the wallet address of the agent"; - + constructor(private solanaKit: SolanaAgentKit) { super(); } @@ -183,25 +191,64 @@ export class SolanaGetWalletAddressTool extends Tool { } } -export class SolanaPumpfunTokenLaunch extends Tool { +export class SolanaPumpfunTokenLaunchTool extends Tool { name = "solana_launch_pumpfun_token"; - description = "Launch a new token on Pump.fun via Solana Agent Kit. Input should be JSON with: {tokenName: string, tokenTicker: string, description?: string, twitter?: string, telegram?: string, website?: string, imageUrl?: string, initialLiquiditySOL?: number, mintAddress?: string}"; + description = "Launch a new token on Pump.fun via Solana Agent Kit. Requires a JSON input with tokenName and tokenTicker, with optional fields for description, twitter, telegram, website, imageUrl, initialLiquiditySOL, and mintAddress."; constructor(private solanaKit: SolanaAgentKit) { - super(); + super(); } - async _call(input: string): Promise { - try { - const options = JSON.parse(input); - await launchPumpFunToken(this.solanaKit, options.tokenName, options.tokenTicker, options); - return "Token launched successfully on Pump.fun"; - } catch (error: any) { - return `Error launching token: ${error.message}`; - } + private validateInput(input : any): void { + if (!input.tokenName || typeof input.tokenName !== 'string') { + throw new Error('tokenName is required and must be a string'); + } + if (!input.tokenTicker || typeof input.tokenTicker !== 'string') { + throw new Error('tokenTicker is required and must be a string'); + } + if (input.initialLiquiditySOL !== undefined && typeof input.initialLiquiditySOL !== 'number') { + throw new Error('initialLiquiditySOL must be a number when provided'); + } + } + + protected async _call(input: string): Promise { + try { + // Parse and normalize input + const parsedInput = toJSON(input); + // Validate the input + this.validateInput(parsedInput); + + // Launch token with validated input + await this.solanaKit.launchPumpFunToken( + parsedInput.tokenName, + parsedInput.tokenTicker, + { + description: parsedInput.description, + twitter: parsedInput.twitter, + telegram: parsedInput.telegram, + website: parsedInput.website, + imageUrl: parsedInput.imageUrl, + initialLiquiditySOL: parsedInput.initialLiquiditySOL, + } + ); + + return JSON.stringify({ + status: "success", + message: "Token launched successfully on Pump.fun", + tokenName: parsedInput.tokenName, + tokenTicker: parsedInput.tokenTicker + }); + + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR" + }); + } } } -// Updated createSolanaTools function + export function createSolanaTools(solanaKit: SolanaAgentKit) { return [ new SolanaBalanceTool(solanaKit), @@ -213,6 +260,6 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaRequestFundsTool(solanaKit), new SolanaRegisterDomainTool(solanaKit), new SolanaGetWalletAddressTool(solanaKit), - new SolanaPumpfunTokenLaunch(solanaKit) + new SolanaPumpfunTokenLaunchTool(solanaKit), ]; } diff --git a/src/tools/launch_pumpfun_token.ts b/src/tools/launch_pumpfun_token.ts index 00ae2c5..d777995 100644 --- a/src/tools/launch_pumpfun_token.ts +++ b/src/tools/launch_pumpfun_token.ts @@ -39,12 +39,15 @@ async function uploadMetadata( finalFormData.append('file', files.file); } + console.log("Final form data:", finalFormData); + const metadataResponse = await fetch("https://pump.fun/api/ipfs", { method: "POST", body: finalFormData }); if (!metadataResponse.ok) { + console.log("Metadata response:", await metadataResponse.json()); throw new Error(`Metadata upload failed: ${metadataResponse.statusText}`); } diff --git a/src/utils/toJSON.ts b/src/utils/toJSON.ts new file mode 100644 index 0000000..2581f4d --- /dev/null +++ b/src/utils/toJSON.ts @@ -0,0 +1,26 @@ +export const toJSON = (str: string): any => { + try { + // Remove curly braces and split by comma + const pairs = str.trim().slice(1, -1).split(","); + + // Convert to object with explicit type + const obj: Record = {}; + + pairs.forEach((pair) => { + const [key, value] = pair + .trim() + .split(":") + .map((s) => s.trim()); + + if (!key || value === undefined) { + throw new Error("Invalid key-value pair format"); + } + + obj[key] = isNaN(Number(value)) ? value : Number(value); + }); + + return JSON.parse(JSON.stringify(obj)); + } catch (error) { + throw new Error(`Failed to parse string to JSON: ${error}`); + } +};