This commit is contained in:
Arihant Bansal
2024-12-23 11:50:14 +05:30
12 changed files with 167 additions and 10 deletions

View File

@@ -180,6 +180,16 @@ import { PublicKey } from "@solana/web3.js";
})(); })();
``` ```
### Fetch Price Data from Pyth
```typescript
const price = await agent.pythFetchPrice(
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
);
console.log("Price in BTC/USD:", price);
```
## Dependencies ## Dependencies
@@ -192,6 +202,7 @@ The toolkit relies on several key Solana and Metaplex libraries:
- @metaplex-foundation/umi - @metaplex-foundation/umi
- @lightprotocol/compressed-token - @lightprotocol/compressed-token
- @lightprotocol/stateless.js - @lightprotocol/stateless.js
- @pythnetwork/price-service-client
## Contributing ## Contributing

View File

@@ -19,7 +19,7 @@ Create a new TypeScript file in the `src/tools/` directory for your tool (e.g.,
### 2. Implement the Tool Class ### 2. Implement the Tool Class
```typescript:src/tools/custom_tool.ts ```typescript:src/langchain/index.ts
import { Tool } from "langchain/tools"; import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../agent"; import { SolanaAgentKit } from "../agent";
@@ -86,6 +86,8 @@ export function createSolanaTools(agent: SolanaAgentKit) {
### 6. Usage Example ### 6. Usage Example
Add a code example in the `README.md` file.
```typescript ```typescript
import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit"; import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";

View File

@@ -28,11 +28,13 @@
"@lightprotocol/stateless.js": "^0.17.1", "@lightprotocol/stateless.js": "^0.17.1",
"@metaplex-foundation/mpl-core": "^1.1.1", "@metaplex-foundation/mpl-core": "^1.1.1",
"@metaplex-foundation/mpl-token-metadata": "^3.3.0", "@metaplex-foundation/mpl-token-metadata": "^3.3.0",
"@metaplex-foundation/mpl-toolbox": "^0.9.4",
"@metaplex-foundation/umi": "^0.9.2", "@metaplex-foundation/umi": "^0.9.2",
"@metaplex-foundation/umi-bundle-defaults": "^0.9.2", "@metaplex-foundation/umi-bundle-defaults": "^0.9.2",
"@metaplex-foundation/umi-web3js-adapters": "^0.9.2", "@metaplex-foundation/umi-web3js-adapters": "^0.9.2",
"@orca-so/common-sdk": "0.6.4", "@orca-so/common-sdk": "0.6.4",
"@orca-so/whirlpools-sdk": "^0.13.12", "@orca-so/whirlpools-sdk": "^0.13.12",
"@pythnetwork/price-service-client": "^1.9.0",
"@raydium-io/raydium-sdk-v2": "0.1.95-alpha", "@raydium-io/raydium-sdk-v2": "0.1.95-alpha",
"@solana/spl-token": "^0.4.9", "@solana/spl-token": "^0.4.9",
"@solana/web3.js": "^1.95.4", "@solana/web3.js": "^1.95.4",

54
pnpm-lock.yaml generated
View File

@@ -38,6 +38,9 @@ importers:
'@metaplex-foundation/mpl-token-metadata': '@metaplex-foundation/mpl-token-metadata':
specifier: ^3.3.0 specifier: ^3.3.0
version: 3.3.0(@metaplex-foundation/umi@0.9.2) version: 3.3.0(@metaplex-foundation/umi@0.9.2)
'@metaplex-foundation/mpl-toolbox':
specifier: ^0.9.4
version: 0.9.4(@metaplex-foundation/umi@0.9.2)
'@metaplex-foundation/umi': '@metaplex-foundation/umi':
specifier: ^0.9.2 specifier: ^0.9.2
version: 0.9.2 version: 0.9.2
@@ -53,6 +56,9 @@ importers:
'@orca-so/whirlpools-sdk': '@orca-so/whirlpools-sdk':
specifier: ^0.13.12 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) 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': '@raydium-io/raydium-sdk-v2':
specifier: 0.1.95-alpha 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) version: 0.1.95-alpha(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)
@@ -316,6 +322,13 @@ packages:
'@solana/web3.js': ^1.90.0 '@solana/web3.js': ^1.90.0
decimal.js: ^10.4.3 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': '@raydium-io/raydium-sdk-v2@0.1.95-alpha':
resolution: {integrity: sha512-+u7yxo/R1JDysTCzOuAlh90ioBe2DlM2Hbcz/tFsxP/YzmnYQzShvNjcmc0361a4zJhmlrEJfpFXW0J3kkX5vA==} resolution: {integrity: sha512-+u7yxo/R1JDysTCzOuAlh90ioBe2DlM2Hbcz/tFsxP/YzmnYQzShvNjcmc0361a4zJhmlrEJfpFXW0J3kkX5vA==}
@@ -592,6 +605,9 @@ packages:
asynckit@0.4.0: asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios-retry@3.9.1:
resolution: {integrity: sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==}
axios@1.7.9: axios@1.7.9:
resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
@@ -826,6 +842,10 @@ packages:
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
is-retry-allowed@2.2.0:
resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==}
engines: {node: '>=10'}
isomorphic-ws@4.0.1: isomorphic-ws@4.0.1:
resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==}
peerDependencies: peerDependencies:
@@ -1126,6 +1146,9 @@ packages:
trim-lines@3.0.1: trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
ts-log@2.2.7:
resolution: {integrity: sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg==}
ts-node@10.9.2: ts-node@10.9.2:
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true hasBin: true
@@ -1576,6 +1599,24 @@ snapshots:
decimal.js: 10.4.3 decimal.js: 10.4.3
tiny-invariant: 1.3.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)': '@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: dependencies:
'@solana/buffer-layout': 4.0.1 '@solana/buffer-layout': 4.0.1
@@ -2016,6 +2057,11 @@ snapshots:
asynckit@0.4.0: {} asynckit@0.4.0: {}
axios-retry@3.9.1:
dependencies:
'@babel/runtime': 7.26.0
is-retry-allowed: 2.2.0
axios@1.7.9: axios@1.7.9:
dependencies: dependencies:
follow-redirects: 1.15.9 follow-redirects: 1.15.9
@@ -2235,10 +2281,16 @@ snapshots:
ipaddr.js@2.2.0: {} ipaddr.js@2.2.0: {}
is-retry-allowed@2.2.0: {}
isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)): isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)):
dependencies: dependencies:
ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) 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): jayson@4.1.3(bufferutil@4.0.8)(utf-8-validate@5.0.10):
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
@@ -2522,6 +2574,8 @@ snapshots:
trim-lines@3.0.1: {} trim-lines@3.0.1: {}
ts-log@2.2.7: {}
ts-node@10.9.2(@types/node@22.10.2)(typescript@5.7.2): ts-node@10.9.2(@types/node@22.10.2)(typescript@5.7.2):
dependencies: dependencies:
'@cspotcode/source-map-support': 0.8.1 '@cspotcode/source-map-support': 0.8.1

View File

@@ -25,8 +25,9 @@ import {
stakeWithJup, stakeWithJup,
sendCompressedAirdrop, sendCompressedAirdrop,
createOrcaSingleSidedWhirlpool, createOrcaSingleSidedWhirlpool,
FEE_TIERS,
fetchPrice, fetchPrice,
pythFetchPrice,
FEE_TIERS,
} from "../tools"; } from "../tools";
import { import {
CollectionDeployment, CollectionDeployment,
@@ -85,7 +86,7 @@ export class SolanaAgentKit {
return deploy_collection(this, options); return deploy_collection(this, options);
} }
async getBalance(token_address?: PublicKey): Promise<number | null> { async getBalance(token_address?: PublicKey): Promise<number> {
return get_balance(this, token_address); return get_balance(this, token_address);
} }
@@ -277,4 +278,8 @@ export class SolanaAgentKit {
tickSize, tickSize,
); );
} }
async pythFetchPrice(priceFeedID: string): Promise<string> {
return pythFetchPrice(priceFeedID);
}
} }

View File

@@ -1,7 +1,7 @@
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
import { Tool } from "langchain/tools"; import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../index"; import { PythFetchPriceResponse, SolanaAgentKit } from "../index";
import { create_image } from "../tools/create_image"; import { create_image } from "../tools/create_image";
import { BN } from "@coral-xyz/anchor"; import { BN } from "@coral-xyz/anchor";
import { FEE_TIERS } from "../tools"; import { FEE_TIERS } from "../tools";
@@ -980,6 +980,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<string> {
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 function createSolanaTools(solanaKit: SolanaAgentKit) { export function createSolanaTools(solanaKit: SolanaAgentKit) {
return [ return [
new SolanaBalanceTool(solanaKit), new SolanaBalanceTool(solanaKit),
@@ -1007,5 +1039,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaRaydiumCreateCpmm(solanaKit), new SolanaRaydiumCreateCpmm(solanaKit),
new SolanaOpenbookCreateMarket(solanaKit), new SolanaOpenbookCreateMarket(solanaKit),
new SolanaCreateSingleSidedWhirlpoolTool(solanaKit), new SolanaCreateSingleSidedWhirlpoolTool(solanaKit),
new SolanaPythFetchPrice(solanaKit),
]; ];
} }

View File

@@ -12,6 +12,7 @@ import {
fromWeb3JsPublicKey, fromWeb3JsPublicKey,
toWeb3JsPublicKey, toWeb3JsPublicKey,
} from "@metaplex-foundation/umi-web3js-adapters"; } from "@metaplex-foundation/umi-web3js-adapters";
import { mplToolbox } from "@metaplex-foundation/mpl-toolbox";
/** /**
* Deploy a new SPL token * Deploy a new SPL token
@@ -33,7 +34,7 @@ export async function deploy_token(
): Promise<{ mint: PublicKey }> { ): Promise<{ mint: PublicKey }> {
try { try {
// Create UMI instance from agent // Create UMI instance from agent
const umi = createUmi(agent.connection.rpcEndpoint); const umi = createUmi(agent.connection.rpcEndpoint).use(mplToolbox());
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet))); umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
// Create new token mint // Create new token mint

View File

@@ -10,7 +10,7 @@ import { SolanaAgentKit } from "../index";
export async function get_balance( export async function get_balance(
agent: SolanaAgentKit, agent: SolanaAgentKit,
token_address?: PublicKey, token_address?: PublicKey,
): Promise<number | null> { ): Promise<number> {
if (!token_address) if (!token_address)
return ( return (
(await agent.connection.getBalance(agent.wallet_address)) / (await agent.connection.getBalance(agent.wallet_address)) /
@@ -19,5 +19,5 @@ export async function get_balance(
const token_account = const token_account =
await agent.connection.getTokenAccountBalance(token_address); await agent.connection.getTokenAccountBalance(token_address);
return token_account.value.uiAmount; return token_account.value.uiAmount || 0;
} }

View File

@@ -20,3 +20,4 @@ export * from "./raydium_create_ammV4";
export * from "./raydium_create_clmm"; export * from "./raydium_create_clmm";
export * from "./raydium_create_cpmm"; export * from "./raydium_create_cpmm";
export * from "./openbook_create_market"; export * from "./openbook_create_market";
export * from "./pyth_fetch_price";

View File

@@ -0,0 +1,40 @@
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(priceFeedID: string): Promise<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}`);
}
}

View File

@@ -77,3 +77,11 @@ export interface FetchPriceResponse {
message?: string; message?: string;
code?: string; code?: string;
} }
export interface PythFetchPriceResponse {
status: "success" | "error";
priceFeedID: string;
price?: string;
message?: string;
code?: string;
}

View File

@@ -70,9 +70,8 @@ async function initializeAgent() {
faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX 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 (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 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://solanaagentkit.xyz/ 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 concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.
explicitly requested.
`, `,
}); });