Merge branch 'gib-bounty-sol033188' of github.com:adpthegreat/solana-agent-kit into gib-bounty-sol033188

This commit is contained in:
aryan
2024-12-23 14:00:14 +05:30
12 changed files with 305 additions and 126 deletions

View File

@@ -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

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
```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";

View File

@@ -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",

51
pnpm-lock.yaml generated
View File

@@ -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

View File

@@ -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<CollectionDeployment> {
return deploy_collection(this, options);
}
async getBalance(token_address?: PublicKey) {
async getBalance(token_address?: PublicKey): Promise<number> {
return get_balance(this, token_address);
}
async mintNFT(
collectionMint: PublicKey,
metadata: Parameters<typeof mintCollectionNFT>[2],
recipient?: PublicKey
) {
recipient?: PublicKey,
): Promise<MintCollectionNFTResponse> {
return mintCollectionNFT(this, collectionMint, metadata, recipient);
}
async transfer(to: PublicKey, amount: number, mint?: PublicKey) {
async transfer(
to: PublicKey,
amount: number,
mint?: PublicKey,
): Promise<string> {
return transfer(this, to, amount, mint);
}
async registerDomain(name: string, spaceKB?: number) {
async registerDomain(name: string, spaceKB?: number): Promise<string> {
return registerDomain(this, name, spaceKB);
}
async resolveSolDomain(domain: string) {
async resolveSolDomain(domain: string): Promise<PublicKey> {
return resolveSolDomain(this, domain);
}
async getPrimaryDomain(account: PublicKey) {
async getPrimaryDomain(account: PublicKey): Promise<string> {
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<string> {
return trade(this, outputMint, inputAmount, inputMint, slippageBps);
}
async lendAssets(amount: number) {
async lendAssets(amount: number): Promise<string> {
return lendAsset(this, amount);
}
async getTPS() {
async getTPS(): Promise<number> {
return getTPS(this);
}
async getTokenDataByAddress(mint: string) {
async getTokenDataByAddress(
mint: string,
): Promise<JupiterTokenData | undefined> {
return getTokenDataByAddress(new PublicKey(mint));
}
async getTokenDataByTicker(ticker: string) {
async getTokenDataByTicker(
ticker: string,
): Promise<JupiterTokenData | undefined> {
return getTokenDataByTicker(ticker);
}
@@ -140,19 +158,19 @@ export class SolanaAgentKit {
tokenTicker: string,
description: string,
imageUrl: string,
options?: PumpFunTokenOptions
) {
options?: PumpFunTokenOptions,
): Promise<PumpfunLaunchResponse> {
return launchPumpFunToken(
this,
tokenName,
tokenTicker,
description,
imageUrl,
options
options,
);
}
async stake(amount: number) {
async stake(amount: number): Promise<string> {
return stakeWithJup(this, amount);
}
@@ -162,7 +180,7 @@ export class SolanaAgentKit {
decimals: number,
recipients: string[],
priorityFeeInLamports: number,
shouldLog: boolean
shouldLog: boolean,
): Promise<string[]> {
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<string> {
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<string> {
return raydiumCreateClmm(
this,
mint1,
mint2,
configId,
initialPrice,
startTime
startTime,
);
}
async raydiumCreateCpmm(
mint1: PublicKey,
mint2: PublicKey,
configId: PublicKey,
mintAAmount: BN,
mintBAmount: BN,
startTime: BN
) {
): Promise<string> {
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<string[]> {
return openbookCreateMarket(
this,
baseMint,
quoteMint,
lotSize,
tickSize
);
tickSize,
)
}
async pythFetchPrice(priceFeedID: string) {
return pythFetchPrice(this, priceFeedID);
}
}

View File

@@ -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<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 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),
];
}

View File

@@ -9,11 +9,15 @@ import { SolanaAgentKit } from "../index";
*/
export async function get_balance(
agent: SolanaAgentKit,
token_address?: PublicKey
) {
token_address?: PublicKey,
): Promise<number> {
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;
}

View File

@@ -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";

View File

@@ -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<any> {
// 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<PumpfunLaunchResponse> {
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;
}
}
}

View File

@@ -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}`);
}
}

View File

@@ -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;
}

View File

@@ -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.
`,