diff --git a/README.md b/README.md index 98708ec..7d49bc6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ An open-source toolkit for connecting AI agents to Solana protocols. Now, any agent, using any model can autonomously perform 15+ Solana actions: - Trade tokens -- Launch new tokens +- Launch new tokens - Lend assets - Send compressed airdrops - Execute blinks @@ -96,10 +96,10 @@ const tools = createSolanaTools(agent); ### Deploy a New Token ```typescript -import { deploy_token } from "solana-agent-kit"; - -const result = await deploy_token( - agent, +const result = await agent.deployToken( + "my ai token", // name + "uri", // uri + "token", // symbol 9, // decimals 1000000 // initial supply ); @@ -110,9 +110,7 @@ console.log("Token Mint Address:", result.mint.toString()); ### Create NFT Collection ```typescript -import { deploy_collection } from "solana-agent-kit"; - -const collection = await deploy_collection(agent, { +const collection = await agent.deployCollection({ name: "My NFT Collection", uri: "https://arweave.net/metadata.json", royaltyBasisPoints: 500, // 5% @@ -128,11 +126,9 @@ const collection = await deploy_collection(agent, { ### Swap Tokens ```typescript -import { trade } from "solana-agent-kit"; import { PublicKey } from "@solana/web3.js"; -const signature = await trade( - agent, +const signature = await agent.trade( new PublicKey("target-token-mint"), 100, // amount new PublicKey("source-token-mint"), @@ -143,46 +139,24 @@ const signature = await trade( ### Lend Tokens ```typescript -import { lendAsset } from "solana-agent-kit"; import { PublicKey } from "@solana/web3.js"; -const signature = await lendAsset( - agent, - 100 // amount +const signature = await agent.lendAssets( + 100 // amount of USDC to lend ); ``` ### Stake SOL ```typescript -import { stakeWithJup } from "solana-agent-kit"; - -const signature = await stakeWithJup( - agent, - 1 // amount in SOL +const signature = await agent.stake( + 1 // amount in SOL to stake ); ``` -### Fetch Token Price - -```typescript -import { fetchPrice } from "solana-agent-kit"; - -const price = await fetchPrice( - agent, - "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" // Token mint address -); - -console.log("Price in USDC:", price); -``` - ### Send an SPL Token Airdrop via ZK Compression ```typescript -import { - sendCompressedAirdrop, - getAirdropCostEstimate, -} from "solana-agent-kit"; import { PublicKey } from "@solana/web3.js"; (async () => { @@ -194,8 +168,7 @@ import { PublicKey } from "@solana/web3.js"; ) ); - const signature = await sendCompressedAirdrop( - agent, + const signature = await agent.sendCompressedAirdrop( new PublicKey("JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"), // mint 42, // amount per recipient [ @@ -207,6 +180,19 @@ import { PublicKey } from "@solana/web3.js"; })(); ``` +### Fetch Price Data from Pyth + +```typescript +import { pythFetchPrice } from "solana-agent-kit"; + +const price = await pythFetchPrice( + agent, + "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" +); + +console.log("Price in BTC/USD:", price); +``` + ## API Reference ### Core Functions @@ -247,6 +233,10 @@ Stake SOL with Jupiter to earn rewards. Send an SPL token airdrop to many recipients at low cost via ZK Compression. +#### `pythFetchPrice(agent, priceFeedID)` + +Fetch price data from Pyth's Hermes service. + ## Dependencies The toolkit relies on several key Solana and Metaplex libraries: @@ -258,6 +248,7 @@ The toolkit relies on several key Solana and Metaplex libraries: - @metaplex-foundation/umi - @lightprotocol/compressed-token - @lightprotocol/stateless.js +- @pythnetwork/price-service-client ## Contributing diff --git a/guides/add_your_own_tool.md b/guides/add_your_own_tool.md index 63d1172..803198c 100644 --- a/guides/add_your_own_tool.md +++ b/guides/add_your_own_tool.md @@ -19,7 +19,7 @@ Create a new TypeScript file in the `src/tools/` directory for your tool (e.g., ### 2. Implement the Tool Class -```typescript:src/tools/custom_tool.ts +```typescript:src/langchain/index.ts import { Tool } from "langchain/tools"; import { SolanaAgentKit } from "../agent"; @@ -86,6 +86,8 @@ export function createSolanaTools(agent: SolanaAgentKit) { ### 6. Usage Example +Add a code example in the `README.md` file. + ```typescript import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit"; diff --git a/package.json b/package.json index 0a8aa00..cd4008d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "tsc", "docs": "typedoc src --out docs", - "test": "ts-node test/index.ts", + "test": "ts-node test/**/*.ts", "generate": "ts-node src/utils/keypair.ts" }, "engines": { @@ -35,6 +35,7 @@ "@onsol/tldparser": "^0.6.7", "@orca-so/common-sdk": "0.6.4", "@orca-so/whirlpools-sdk": "^0.13.12", + "@pythnetwork/price-service-client": "^1.9.0", "@raydium-io/raydium-sdk-v2": "0.1.95-alpha", "@solana/spl-token": "^0.4.9", "@solana/web3.js": "^1.95.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 256e519..ddaf940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: '@orca-so/whirlpools-sdk': specifier: ^0.13.12 version: 0.13.12(@coral-xyz/anchor@0.29.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@orca-so/common-sdk@0.6.4(@solana/spl-token@0.4.9(@solana/web3.js@1.98.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10))(@solana/web3.js@1.98.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(decimal.js@10.4.3))(@solana/spl-token@0.4.9(@solana/web3.js@1.98.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10))(@solana/web3.js@1.98.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(decimal.js@10.4.3) + '@pythnetwork/price-service-client': + specifier: ^1.9.0 + version: 1.9.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@raydium-io/raydium-sdk-v2': specifier: 0.1.95-alpha version: 0.1.95-alpha(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) @@ -352,6 +355,13 @@ packages: '@solana/web3.js': ^1.90.0 decimal.js: ^10.4.3 + '@pythnetwork/price-service-client@1.9.0': + resolution: {integrity: sha512-SLm3IFcfmy9iMqHeT4Ih6qMNZhJEefY14T9yTlpsH2D/FE5+BaGGnfcexUifVlfH6M7mwRC4hEFdNvZ6ebZjJg==} + deprecated: This package is deprecated and is no longer maintained. Please use @pythnetwork/hermes-client instead. + + '@pythnetwork/price-service-sdk@1.8.0': + resolution: {integrity: sha512-tFZ1thj3Zja06DzPIX2dEWSi7kIfIyqreoywvw5NQ3Z1pl5OJHQGMEhxt6Li3UCGSp2ooYZS9wl8/8XfrfrNSA==} + '@raydium-io/raydium-sdk-v2@0.1.95-alpha': resolution: {integrity: sha512-+u7yxo/R1JDysTCzOuAlh90ioBe2DlM2Hbcz/tFsxP/YzmnYQzShvNjcmc0361a4zJhmlrEJfpFXW0J3kkX5vA==} @@ -648,6 +658,9 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + axios-retry@3.9.1: + resolution: {integrity: sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==} + axios@1.7.9: resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} @@ -990,6 +1003,10 @@ packages: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} + is-retry-allowed@2.2.0: + resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==} + engines: {node: '>=10'} + is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -1328,6 +1345,9 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + ts-log@2.2.7: + resolution: {integrity: sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg==} + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -1832,6 +1852,24 @@ snapshots: decimal.js: 10.4.3 tiny-invariant: 1.3.3 + '@pythnetwork/price-service-client@1.9.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@pythnetwork/price-service-sdk': 1.8.0 + '@types/ws': 8.5.13 + axios: 1.7.9 + axios-retry: 3.9.1 + isomorphic-ws: 4.0.1(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + ts-log: 2.2.7 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@pythnetwork/price-service-sdk@1.8.0': + dependencies: + bn.js: 5.2.1 + '@raydium-io/raydium-sdk-v2@0.1.95-alpha(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -2294,6 +2332,11 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + axios-retry@3.9.1: + dependencies: + '@babel/runtime': 7.26.0 + is-retry-allowed: 2.2.0 + axios@1.7.9: dependencies: follow-redirects: 1.15.9 @@ -2630,6 +2673,8 @@ snapshots: call-bind: 1.0.8 define-properties: 1.2.1 + is-retry-allowed@2.2.0: {} + is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.18 @@ -2638,6 +2683,10 @@ snapshots: dependencies: ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) + isomorphic-ws@4.0.1(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)): + dependencies: + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + jayson@4.1.3(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@types/connect': 3.4.38 @@ -2956,6 +3005,8 @@ snapshots: trim-lines@3.0.1: {} + ts-log@2.2.7: {} + ts-node@10.9.2(@types/node@22.10.2)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 diff --git a/src/agent/index.ts b/src/agent/index.ts index b4b7f80..9ecf699 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -1,4 +1,4 @@ -import { Connection, Keypair, PublicKey } from "@solana/web3.js";; +import { Connection, Keypair, PublicKey } from "@solana/web3.js"; import bs58 from "bs58"; import Decimal from "decimal.js"; import { DEFAULT_OPTIONS } from "../constants"; @@ -26,6 +26,7 @@ import { sendCompressedAirdrop, createOrcaSingleSidedWhirlpool, FEE_TIERS, + pythFetchPrice, getAllDomainsTLDs, getAllRegisteredAllDomains, getOwnedDomainsForTLD, @@ -33,7 +34,14 @@ import { getOwnedAllDomains, resolveAllDomains, } from "../tools"; -import { CollectionOptions, PumpFunTokenOptions } from "../types"; +import { + CollectionDeployment, + CollectionOptions, + JupiterTokenData, + MintCollectionNFTResponse, + PumpfunLaunchResponse, + PumpFunTokenOptions, +} from "../types"; import { BN } from "@coral-xyz/anchor"; import { NameAccountAndDomain } from "@onsol/tldparser"; @@ -55,7 +63,7 @@ export class SolanaAgentKit { constructor( private_key: string, rpc_url = "https://api.mainnet-beta.solana.com", - openai_api_key: string + openai_api_key: string, ) { this.connection = new Connection(rpc_url); this.wallet = Keypair.fromSecretKey(bs58.decode(private_key)); @@ -73,40 +81,46 @@ export class SolanaAgentKit { uri: string, symbol: string, decimals: number = DEFAULT_OPTIONS.TOKEN_DECIMALS, - initialSupply?: number - ) { + initialSupply?: number, + ): Promise<{ mint: PublicKey }> { return deploy_token(this, name, uri, symbol, decimals, initialSupply); } - async deployCollection(options: CollectionOptions) { + async deployCollection( + options: CollectionOptions, + ): Promise { return deploy_collection(this, options); } - async getBalance(token_address?: PublicKey) { + async getBalance(token_address?: PublicKey): Promise { return get_balance(this, token_address); } async mintNFT( collectionMint: PublicKey, metadata: Parameters[2], - recipient?: PublicKey - ) { + recipient?: PublicKey, + ): Promise { return mintCollectionNFT(this, collectionMint, metadata, recipient); } - async transfer(to: PublicKey, amount: number, mint?: PublicKey) { + async transfer( + to: PublicKey, + amount: number, + mint?: PublicKey, + ): Promise { return transfer(this, to, amount, mint); } - async registerDomain(name: string, spaceKB?: number) { + async registerDomain(name: string, spaceKB?: number): Promise { return registerDomain(this, name, spaceKB); } - async resolveSolDomain(domain: string) { + async resolveSolDomain(domain: string): Promise { return resolveSolDomain(this, domain); } - async getPrimaryDomain(account: PublicKey) { + async getPrimaryDomain(account: PublicKey): Promise { return getPrimaryDomain(this, account); } @@ -114,24 +128,28 @@ export class SolanaAgentKit { outputMint: PublicKey, inputAmount: number, inputMint?: PublicKey, - slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS - ) { + slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS, + ): Promise { return trade(this, outputMint, inputAmount, inputMint, slippageBps); } - async lendAssets(amount: number) { + async lendAssets(amount: number): Promise { return lendAsset(this, amount); } - async getTPS() { + async getTPS(): Promise { return getTPS(this); } - async getTokenDataByAddress(mint: string) { + async getTokenDataByAddress( + mint: string, + ): Promise { return getTokenDataByAddress(new PublicKey(mint)); } - async getTokenDataByTicker(ticker: string) { + async getTokenDataByTicker( + ticker: string, + ): Promise { return getTokenDataByTicker(ticker); } @@ -140,19 +158,19 @@ export class SolanaAgentKit { tokenTicker: string, description: string, imageUrl: string, - options?: PumpFunTokenOptions - ) { + options?: PumpFunTokenOptions, + ): Promise { return launchPumpFunToken( this, tokenName, tokenTicker, description, imageUrl, - options + options, ); } - async stake(amount: number) { + async stake(amount: number): Promise { return stakeWithJup(this, amount); } @@ -162,7 +180,7 @@ export class SolanaAgentKit { decimals: number, recipients: string[], priorityFeeInLamports: number, - shouldLog: boolean + shouldLog: boolean, ): Promise { return await sendCompressedAirdrop( this, @@ -171,7 +189,7 @@ export class SolanaAgentKit { decimals, recipients.map((recipient) => new PublicKey(recipient)), priorityFeeInLamports, - shouldLog + shouldLog, ); } @@ -227,12 +245,10 @@ export class SolanaAgentKit { async raydiumCreateAmmV4( marketId: PublicKey, - baseAmount: BN, quoteAmount: BN, - startTime: BN - ) { + ): Promise { return raydiumCreateAmmV4( this, marketId, @@ -241,50 +257,39 @@ export class SolanaAgentKit { quoteAmount, startTime - ); + );; } async raydiumCreateClmm( mint1: PublicKey, mint2: PublicKey, - configId: PublicKey, - initialPrice: Decimal, startTime: BN - ) { + ): Promise { return raydiumCreateClmm( this, - mint1, mint2, - configId, - initialPrice, - startTime + startTime, ); } async raydiumCreateCpmm( mint1: PublicKey, mint2: PublicKey, - configId: PublicKey, - mintAAmount: BN, mintBAmount: BN, - startTime: BN - ) { + ): Promise { return raydiumCreateCpmm( this, - mint1, mint2, - configId, - mintAAmount, mintBAmount, @@ -295,17 +300,20 @@ export class SolanaAgentKit { async openbookCreateMarket( baseMint: PublicKey, quoteMint: PublicKey, - lotSize: number = 1, tickSize: number = 0.01 - ) { + ): Promise { return openbookCreateMarket( this, baseMint, quoteMint, lotSize, - tickSize - ); + tickSize, + ) + } + + async pythFetchPrice(priceFeedID: string) { + return pythFetchPrice(this, priceFeedID); } } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 50403c9..9a34f77 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -1,7 +1,7 @@ import { PublicKey } from "@solana/web3.js"; import Decimal from "decimal.js"; import { Tool } from "langchain/tools"; -import { SolanaAgentKit } from "../index"; +import { PythFetchPriceResponse, SolanaAgentKit } from "../index"; import { create_image } from "../tools/create_image"; import { fetchPrice } from "../tools/fetch_price"; import { BN } from "@coral-xyz/anchor"; @@ -981,6 +981,38 @@ export class SolanaOpenbookCreateMarket extends Tool { } } +export class SolanaPythFetchPrice extends Tool { + name = "solana_pyth_fetch_price"; + description = `Fetch the price of a given price feed from Pyth's Hermes service + + Inputs: + priceFeedID: string, the price feed ID, e.g., "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" for BTC/USD`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + async _call(input: string): Promise { + try { + const price = await this.solanaKit.pythFetchPrice(input); + let response: PythFetchPriceResponse = { + status: "success", + priceFeedID: input, + price: price, + }; + return JSON.stringify(response); + } catch (error: any) { + let response: PythFetchPriceResponse = { + status: "error", + priceFeedID: input, + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }; + return JSON.stringify(response); + } + } +} + export class SolanaResolveAllDomainsTool extends Tool { name = "solana_resolve_all_domains"; description = `Resolve a domain name to a public key. @@ -1190,6 +1222,13 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaRaydiumCreateCpmm(solanaKit), new SolanaOpenbookCreateMarket(solanaKit), new SolanaCreateSingleSidedWhirlpoolTool(solanaKit), + new SolanaPythFetchPrice(solanaKit), + new SolanaResolveDomainTool(solanaKit), + new SolanaGetOwnedDomains(solanaKit), + new SolanaGetOwnedTldDomains(solanaKit), + new SolanaGetAllTlds(solanaKit), + new SolanaGetAllRegisteredDomains(solanaKit), + new SolanaGetMainDomain(solanaKit), new SolanaResolveAllDomainsTool(solanaKit), new SolanaGetOwnedDomains(solanaKit), new SolanaGetOwnedTldDomains(solanaKit), @@ -1198,3 +1237,4 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaGetMainDomain(solanaKit), ]; } + diff --git a/src/tools/get_balance.ts b/src/tools/get_balance.ts index 2bdf3f2..05a56a1 100644 --- a/src/tools/get_balance.ts +++ b/src/tools/get_balance.ts @@ -9,11 +9,15 @@ import { SolanaAgentKit } from "../index"; */ export async function get_balance( agent: SolanaAgentKit, - token_address?: PublicKey -) { + token_address?: PublicKey, +): Promise { if (!token_address) - return await agent.connection.getBalance(agent.wallet_address) / LAMPORTS_PER_SOL + return ( + (await agent.connection.getBalance(agent.wallet_address)) / + LAMPORTS_PER_SOL + ); - const token_account = await agent.connection.getTokenAccountBalance(token_address); - return token_account.value.uiAmount; + const token_account = + await agent.connection.getTokenAccountBalance(token_address); + return token_account.value.uiAmount || 0; } diff --git a/src/tools/index.ts b/src/tools/index.ts index e009086..846c92b 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -15,7 +15,6 @@ export * from "./get_token_data"; export * from "./stake_with_jup"; export * from "./fetch_price"; export * from "./send_compressed_airdrop"; - export * from "./create_orca_single_sided_whirlpool"; export * from "./get_all_domains_tlds"; export * from "./get_all_registered_all_domains"; @@ -24,7 +23,16 @@ export * from "./get_main_all_domains_domain"; export * from "./get_owned_all_domains"; export * from "./resolve_domain"; + +export * from "./get_all_domains_tlds"; +export * from "./get_all_registered_all_domains"; +export * from "./get_owned_domains_for_tld"; +export * from "./get_main_all_domains_domain"; +export * from "./get_owned_all_domains"; +export * from "./resolve_domain"; + export * from "./raydium_create_ammV4"; export * from "./raydium_create_clmm"; export * from "./raydium_create_cpmm"; export * from "./openbook_create_market"; +export * from "./pyth_fetch_price"; diff --git a/src/tools/launch_pumpfun_token.ts b/src/tools/launch_pumpfun_token.ts index 397a177..0c3e6f5 100644 --- a/src/tools/launch_pumpfun_token.ts +++ b/src/tools/launch_pumpfun_token.ts @@ -1,23 +1,27 @@ // src/tools/launch_pumpfun_token.ts import { VersionedTransaction, Keypair } from "@solana/web3.js"; -import { PumpFunTokenOptions, SolanaAgentKit } from "../index"; +import { + PumpfunLaunchResponse, + PumpFunTokenOptions, + SolanaAgentKit, +} from "../index"; async function uploadMetadata( - tokenName: string, + tokenName: string, tokenTicker: string, description: string, imageUrl: string, - options?: PumpFunTokenOptions + options?: PumpFunTokenOptions, ): Promise { // Create metadata object const formData = new URLSearchParams(); - formData.append('name', tokenName); + formData.append("name", tokenName); formData.append("symbol", tokenTicker); formData.append("description", description); formData.append("showName", "true"); - if (options?.twitter) formData.append('twitter', options.twitter); + if (options?.twitter) formData.append("twitter", options.twitter); if (options?.telegram) formData.append("telegram", options.telegram); if (options?.website) formData.append("website", options.website); @@ -35,13 +39,12 @@ async function uploadMetadata( } // Add file if exists if (files?.file) { - finalFormData.append('file', files.file); + finalFormData.append("file", files.file); } - const metadataResponse = await fetch("https://pump.fun/api/ipfs", { method: "POST", - body: finalFormData + body: finalFormData, }); if (!metadataResponse.ok) { @@ -55,7 +58,7 @@ async function createTokenTransaction( agent: SolanaAgentKit, mintKeypair: Keypair, metadataResponse: any, - options?: PumpFunTokenOptions + options?: PumpFunTokenOptions, ) { const payload = { publicKey: agent.wallet_address.toBase58(), @@ -78,12 +81,14 @@ async function createTokenTransaction( headers: { "Content-Type": "application/json", }, - body: JSON.stringify(payload) + body: JSON.stringify(payload), }); if (!response.ok) { const errorText = await response.text(); - throw new Error(`Transaction creation failed: ${response.status} - ${errorText}`); + throw new Error( + `Transaction creation failed: ${response.status} - ${errorText}`, + ); } return response; @@ -92,12 +97,13 @@ async function createTokenTransaction( async function signAndSendTransaction( kit: SolanaAgentKit, tx: VersionedTransaction, - mintKeypair: Keypair + mintKeypair: Keypair, ) { try { // Get the latest blockhash - const { blockhash, lastValidBlockHeight } = await kit.connection.getLatestBlockhash(); - + const { blockhash, lastValidBlockHeight } = + await kit.connection.getLatestBlockhash(); + // Update transaction with latest blockhash tx.message.recentBlockhash = blockhash; @@ -107,15 +113,15 @@ async function signAndSendTransaction( // Send and confirm transaction with options const signature = await kit.connection.sendTransaction(tx, { skipPreflight: false, - preflightCommitment: 'confirmed', - maxRetries: 5 + preflightCommitment: "confirmed", + maxRetries: 5, }); // Wait for confirmation const confirmation = await kit.connection.confirmTransaction({ signature, blockhash, - lastValidBlockHeight + lastValidBlockHeight, }); if (confirmation.value.err) { @@ -124,9 +130,9 @@ async function signAndSendTransaction( return signature; } catch (error) { - console.error('Transaction send error:', error); - if (error instanceof Error && 'logs' in error) { - console.error('Transaction logs:', error.logs); + console.error("Transaction send error:", error); + if (error instanceof Error && "logs" in error) { + console.error("Transaction logs:", error.logs); } throw error; } @@ -140,6 +146,7 @@ async function signAndSendTransaction( * @param description - Description of the token * @param imageUrl - URL of the token image * @param options - Optional token options (twitter, telegram, website, initialLiquiditySOL, slippageBps, priorityFee) + * @returns - Signature of the transaction, mint address and metadata URI, if successful, else error */ export async function launchPumpFunToken( agent: SolanaAgentKit, @@ -147,15 +154,27 @@ export async function launchPumpFunToken( tokenTicker: string, description: string, imageUrl: string, - options?: PumpFunTokenOptions -) { + options?: PumpFunTokenOptions, +): Promise { try { - const mintKeypair = Keypair.generate(); - const metadataResponse = await uploadMetadata(tokenName, tokenTicker, description, imageUrl, options); - const response = await createTokenTransaction(agent, mintKeypair, metadataResponse, options); + const metadataResponse = await uploadMetadata( + tokenName, + tokenTicker, + description, + imageUrl, + options, + ); + const response = await createTokenTransaction( + agent, + mintKeypair, + metadataResponse, + options, + ); const transactionData = await response.arrayBuffer(); - const tx = VersionedTransaction.deserialize(new Uint8Array(transactionData)); + const tx = VersionedTransaction.deserialize( + new Uint8Array(transactionData), + ); const signature = await signAndSendTransaction(agent, tx, mintKeypair); return { @@ -163,12 +182,11 @@ export async function launchPumpFunToken( mint: mintKeypair.publicKey.toBase58(), metadataUri: metadataResponse.metadataUri, }; - } catch (error) { console.error("Error in launchpumpfuntoken:", error); - if (error instanceof Error && 'logs' in error) { - console.error('Transaction logs:', (error as any).logs); + if (error instanceof Error && "logs" in error) { + console.error("Transaction logs:", (error as any).logs); } throw error; } -} \ No newline at end of file +} diff --git a/src/tools/pyth_fetch_price.ts b/src/tools/pyth_fetch_price.ts new file mode 100644 index 0000000..5ff7317 --- /dev/null +++ b/src/tools/pyth_fetch_price.ts @@ -0,0 +1,48 @@ +import { PublicKey } from "@solana/web3.js"; +import { SolanaAgentKit } from "../index"; +import { Tool } from "langchain/tools"; +import { PriceServiceConnection } from "@pythnetwork/price-service-client"; +import BN from "bn.js"; + +/** + * Fetch the price of a given price feed from Pyth + * @param agent SolanaAgentKit instance + * @param priceFeedID Price feed ID + * @returns Latest price value from feed + * + * You can find priceFeedIDs here: https://www.pyth.network/developers/price-feed-ids#stable + */ +export async function pythFetchPrice( + agent: SolanaAgentKit, + priceFeedID: string +) { + // get Hermes service URL from https://docs.pyth.network/price-feeds/api-instances-and-providers/hermes + const stableHermesServiceUrl: string = "https://hermes.pyth.network"; + const connection = new PriceServiceConnection(stableHermesServiceUrl); + const feeds = [priceFeedID]; + + try { + const currentPrice = await connection.getLatestPriceFeeds(feeds); + + if (currentPrice === undefined) { + throw new Error("Price data not available for the given token."); + } + + if (currentPrice.length === 0) { + throw new Error("Price data not available for the given token."); + } + + // get price and exponent from price feed + let price = new BN(currentPrice[0].getPriceUnchecked().price); + let exponent = new BN(currentPrice[0].getPriceUnchecked().expo); + + // convert to scaled price + let scaledPrice = price.div(new BN(10).pow(exponent)); + + return scaledPrice.toString(); + } catch (error: any) { + throw new Error(`Fetching price from Pyth failed: ${error.message}`); + } +} + + diff --git a/src/types/index.ts b/src/types/index.ts index 28d64cb..6c9dc16 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -77,3 +77,11 @@ export interface FetchPriceResponse { message?: string; code?: string; } + +export interface PythFetchPriceResponse { + status: "success" | "error"; + priceFeedID: string; + price?: string; + message?: string; + code?: string; +} diff --git a/test/index.ts b/test/index.ts index 9522c23..00faade 100644 --- a/test/index.ts +++ b/test/index.ts @@ -70,7 +70,7 @@ async function initializeAgent() { faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone asks you to do something you can't do with your currently available tools, you must say so, and encourage them to implement it - themselves using the Solana Agent Kit, recommend they go to https://sendai.fun/kit for more information. Be + 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. `,