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

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