mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-18 15:10:33 +00:00
Merge branch 'main' into calintje/main
This commit is contained in:
@@ -9,10 +9,15 @@ import {
|
||||
transfer,
|
||||
trade,
|
||||
registerDomain,
|
||||
resolveSolDomain,
|
||||
getPrimaryDomain,
|
||||
launchPumpFunToken,
|
||||
lendAsset,
|
||||
getTPS,
|
||||
getTokenDataByAddress,
|
||||
getTokenDataByTicker,
|
||||
stakeWithJup,
|
||||
sendCompressedAirdrop,
|
||||
createOrcaSingleSidedWhirlpool,
|
||||
FEE_TIERS
|
||||
} from "../tools";
|
||||
@@ -39,7 +44,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));
|
||||
@@ -53,10 +58,13 @@ export class SolanaAgentKit {
|
||||
}
|
||||
|
||||
async deployToken(
|
||||
name: string,
|
||||
uri: string,
|
||||
symbol: string,
|
||||
decimals: number = DEFAULT_OPTIONS.TOKEN_DECIMALS,
|
||||
// initialSupply?: number
|
||||
initialSupply?: number
|
||||
) {
|
||||
return deploy_token(this, decimals);
|
||||
return deploy_token(this, name, uri, symbol, decimals, initialSupply);
|
||||
}
|
||||
|
||||
async deployCollection(options: CollectionOptions) {
|
||||
@@ -70,7 +78,7 @@ export class SolanaAgentKit {
|
||||
async mintNFT(
|
||||
collectionMint: PublicKey,
|
||||
metadata: Parameters<typeof mintCollectionNFT>[2],
|
||||
recipient?: PublicKey,
|
||||
recipient?: PublicKey
|
||||
) {
|
||||
return mintCollectionNFT(this, collectionMint, metadata, recipient);
|
||||
}
|
||||
@@ -83,11 +91,19 @@ export class SolanaAgentKit {
|
||||
return registerDomain(this, name, spaceKB);
|
||||
}
|
||||
|
||||
async resolveSolDomain(domain: string) {
|
||||
return resolveSolDomain(this, domain);
|
||||
}
|
||||
|
||||
async getPrimaryDomain(account: PublicKey) {
|
||||
return getPrimaryDomain(this, account);
|
||||
}
|
||||
|
||||
async trade(
|
||||
outputMint: PublicKey,
|
||||
inputAmount: number,
|
||||
inputMint?: PublicKey,
|
||||
slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS,
|
||||
slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS
|
||||
) {
|
||||
return trade(this, outputMint, inputAmount, inputMint, slippageBps);
|
||||
}
|
||||
@@ -100,12 +116,20 @@ export class SolanaAgentKit {
|
||||
return getTPS(this);
|
||||
}
|
||||
|
||||
async getTokenDataByAddress(mint: string) {
|
||||
return getTokenDataByAddress(new PublicKey(mint));
|
||||
}
|
||||
|
||||
async getTokenDataByTicker(ticker: string) {
|
||||
return getTokenDataByTicker(ticker);
|
||||
}
|
||||
|
||||
async launchPumpFunToken(
|
||||
tokenName: string,
|
||||
tokenTicker: string,
|
||||
description: string,
|
||||
imageUrl: string,
|
||||
options?: PumpFunTokenOptions,
|
||||
options?: PumpFunTokenOptions
|
||||
) {
|
||||
return launchPumpFunToken(
|
||||
this,
|
||||
@@ -113,16 +137,33 @@ export class SolanaAgentKit {
|
||||
tokenTicker,
|
||||
description,
|
||||
imageUrl,
|
||||
options,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
async stake(
|
||||
amount: number,
|
||||
) {
|
||||
|
||||
async stake(amount: number) {
|
||||
return stakeWithJup(this, amount);
|
||||
}
|
||||
|
||||
async sendCompressedAirdrop(
|
||||
mintAddress: string,
|
||||
amount: number,
|
||||
decimals: number,
|
||||
recipients: string[],
|
||||
priorityFeeInLamports: number,
|
||||
shouldLog: boolean
|
||||
): Promise<string[]> {
|
||||
return await sendCompressedAirdrop(
|
||||
this,
|
||||
new PublicKey(mintAddress),
|
||||
amount,
|
||||
decimals,
|
||||
recipients.map((recipient) => new PublicKey(recipient)),
|
||||
priorityFeeInLamports,
|
||||
shouldLog
|
||||
);
|
||||
}
|
||||
|
||||
async createOrcaSingleSidedWhirlpool(
|
||||
depositTokenAmount: BN,
|
||||
depositTokenMint: PublicKey,
|
||||
|
||||
@@ -90,38 +90,30 @@ 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 token on Solana blockchain.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
name: string, eg "My Token" (required)
|
||||
uri: string, eg "https://example.com/token.json" (required)
|
||||
symbol: string, eg "MTK" (required)
|
||||
decimals?: number, eg 9 (optional, defaults to 9)
|
||||
initialSupply?: number, eg 1000000 (optional)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
private validateInput(input: any): void {
|
||||
if (
|
||||
input.decimals !== undefined &&
|
||||
(typeof input.decimals !== "number" ||
|
||||
input.decimals < 0 ||
|
||||
input.decimals > 9)
|
||||
) {
|
||||
throw new Error(
|
||||
"decimals must be a number between 0 and 9 when provided"
|
||||
);
|
||||
}
|
||||
if (
|
||||
input.initialSupply !== undefined &&
|
||||
(typeof input.initialSupply !== "number" || input.initialSupply <= 0)
|
||||
) {
|
||||
throw new Error("initialSupply must be a positive number when provided");
|
||||
}
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = toJSON(input);
|
||||
this.validateInput(parsedInput);
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const result = await this.solanaKit.deployToken(parsedInput.decimals);
|
||||
const result = await this.solanaKit.deployToken(
|
||||
parsedInput.name,
|
||||
parsedInput.uri,
|
||||
parsedInput.symbol,
|
||||
parsedInput.decimals,
|
||||
parsedInput.initialSupply
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
@@ -141,57 +133,20 @@ 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 on Solana blockchain.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
name: string, eg "My Collection" (required)
|
||||
uri: string, eg "https://example.com/collection.json" (required)
|
||||
royaltyBasisPoints?: number, eg 500 for 5% (optional)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
private validateInput(input: any): void {
|
||||
if (!input.name || typeof input.name !== "string") {
|
||||
throw new Error("name is required and must be a string");
|
||||
}
|
||||
if (!input.uri || typeof input.uri !== "string") {
|
||||
throw new Error("uri is required and must be a string");
|
||||
}
|
||||
if (
|
||||
input.royaltyBasisPoints !== undefined &&
|
||||
(typeof input.royaltyBasisPoints !== "number" ||
|
||||
input.royaltyBasisPoints < 0 ||
|
||||
input.royaltyBasisPoints > 10000)
|
||||
) {
|
||||
throw new Error(
|
||||
"royaltyBasisPoints must be a number between 0 and 10000 when provided"
|
||||
);
|
||||
}
|
||||
if (input.creators) {
|
||||
if (!Array.isArray(input.creators)) {
|
||||
throw new Error("creators must be an array when provided");
|
||||
}
|
||||
input.creators.forEach((creator: any, index: number) => {
|
||||
if (!creator.address || typeof creator.address !== "string") {
|
||||
throw new Error(
|
||||
`creator[${index}].address is required and must be a string`
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof creator.percentage !== "number" ||
|
||||
creator.percentage < 0 ||
|
||||
creator.percentage > 100
|
||||
) {
|
||||
throw new Error(
|
||||
`creator[${index}].percentage must be a number between 0 and 100`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = toJSON(input);
|
||||
this.validateInput(parsedInput);
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const result = await this.solanaKit.deployCollection(parsedInput);
|
||||
|
||||
@@ -213,50 +168,42 @@ 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 on Solana blockchain.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
collectionMint: string, eg "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w" (required) - The address of the collection to mint into
|
||||
name: string, eg "My NFT" (required)
|
||||
uri: string, eg "https://example.com/nft.json" (required)
|
||||
recipient?: string, eg "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u" (optional) - The wallet to receive the NFT, defaults to agent's wallet which is ${this.solanaKit.wallet_address.toString()}`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
private validateInput(input: any): void {
|
||||
if (!input.collectionMint || typeof input.collectionMint !== "string") {
|
||||
throw new Error("collectionMint is required and must be a string");
|
||||
}
|
||||
if (!input.metadata || typeof input.metadata !== "object") {
|
||||
throw new Error("metadata is required and must be an object");
|
||||
}
|
||||
if (!input.metadata.name || typeof input.metadata.name !== "string") {
|
||||
throw new Error("metadata.name is required and must be a string");
|
||||
}
|
||||
if (!input.metadata.symbol || typeof input.metadata.symbol !== "string") {
|
||||
throw new Error("metadata.symbol is required and must be a string");
|
||||
}
|
||||
if (!input.metadata.uri || typeof input.metadata.uri !== "string") {
|
||||
throw new Error("metadata.uri is required and must be a string");
|
||||
}
|
||||
if (input.recipient !== undefined && typeof input.recipient !== "string") {
|
||||
throw new Error("recipient must be a string when provided");
|
||||
}
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = toJSON(input);
|
||||
this.validateInput(parsedInput);
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const result = await this.solanaKit.mintNFT(
|
||||
new PublicKey(parsedInput.collectionMint),
|
||||
parsedInput.metadata,
|
||||
parsedInput.recipient ? new PublicKey(parsedInput.recipient) : undefined
|
||||
{
|
||||
name: parsedInput.name,
|
||||
uri: parsedInput.uri,
|
||||
},
|
||||
parsedInput.recipient
|
||||
? new PublicKey(parsedInput.recipient)
|
||||
: this.solanaKit.wallet_address
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: result.mint.toString(),
|
||||
name: parsedInput.metadata.name,
|
||||
metadata: {
|
||||
name: parsedInput.name,
|
||||
symbol: parsedInput.symbol,
|
||||
uri: parsedInput.uri,
|
||||
},
|
||||
recipient: parsedInput.recipient || result.mint.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -393,6 +340,70 @@ export class SolanaRegisterDomainTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaResolveDomainTool extends Tool {
|
||||
name = "solana_resolve_domain";
|
||||
description = `Resolve a .sol domain to a Solana PublicKey.
|
||||
|
||||
Inputs:
|
||||
domain: string, eg "pumpfun.sol" or "pumpfun"(required)
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const domain = input.trim();
|
||||
const publicKey = await this.solanaKit.resolveSolDomain(domain);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Domain resolved successfully",
|
||||
publicKey: publicKey.toBase58(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaGetDomainTool extends Tool {
|
||||
name = "solana_get_domain";
|
||||
description = `Retrieve the .sol domain associated for a given account address.
|
||||
|
||||
Inputs:
|
||||
account: string, eg "4Be9CvxqHW6BYiRAxW9Q3xu1ycTMWaL5z8NX4HR3ha7t" (required)
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const account = new PublicKey(input.trim());
|
||||
const domain = await this.solanaKit.getPrimaryDomain(account);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Primary domain retrieved successfully",
|
||||
domain,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaGetWalletAddressTool extends Tool {
|
||||
name = "solana_get_wallet_address";
|
||||
description = `Get the wallet address of the agent`;
|
||||
@@ -634,6 +645,110 @@ export class SolanaFetchPriceTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaTokenDataTool extends Tool {
|
||||
name = "solana_token_data";
|
||||
description = `Get the token data for a given token mint address
|
||||
|
||||
Inputs: mintAddress is required.
|
||||
mintAddress: string, eg "So11111111111111111111111111111111111111112" (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = input.trim();
|
||||
|
||||
const tokenData = await this.solanaKit.getTokenDataByAddress(parsedInput);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
tokenData: tokenData,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaTokenDataByTickerTool extends Tool {
|
||||
name = "solana_token_data_by_ticker";
|
||||
description = `Get the token data for a given token ticker
|
||||
|
||||
Inputs: ticker is required.
|
||||
ticker: string, eg "USDC" (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const ticker = input.trim();
|
||||
const tokenData = await this.solanaKit.getTokenDataByTicker(ticker);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
tokenData: tokenData,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCompressedAirdropTool extends Tool {
|
||||
name = "solana_compressed_airdrop";
|
||||
description = `Airdrop SPL tokens with ZK Compression (also called as airdropping tokens)
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
mintAddress: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" (required)
|
||||
amount: number, the amount of tokens to airdrop per recipient, e.g., 42 (required)
|
||||
decimals: number, the decimals of the token, e.g., 6 (required)
|
||||
recipients: string[], the recipient addresses, e.g., ["1nc1nerator11111111111111111111111111111111"] (required)
|
||||
priorityFeeInLamports: number, the priority fee in lamports. Default is 30_000. (optional)
|
||||
shouldLog: boolean, whether to log progress to stdout. Default is false. (optional)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const txs = await this.solanaKit.sendCompressedAirdrop(
|
||||
parsedInput.mintAddress,
|
||||
parsedInput.amount,
|
||||
parsedInput.decimals,
|
||||
parsedInput.recipients,
|
||||
parsedInput.priorityFeeInLamports || 30_000,
|
||||
parsedInput.shouldLog || false
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `Airdropped ${parsedInput.amount} tokens to ${parsedInput.recipients.length} recipients.`,
|
||||
transactionHashes: txs,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
|
||||
name = "create_orca_single_sided_whirlpool";
|
||||
description = `Create a single-sided Whirlpool with liquidity.
|
||||
@@ -706,5 +821,10 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaTPSCalculatorTool(solanaKit),
|
||||
new SolanaStakeTool(solanaKit),
|
||||
new SolanaFetchPriceTool(solanaKit),
|
||||
new SolanaResolveDomainTool(solanaKit),
|
||||
new SolanaGetDomainTool(solanaKit),
|
||||
new SolanaTokenDataTool(solanaKit),
|
||||
new SolanaTokenDataByTickerTool(solanaKit),
|
||||
new SolanaCompressedAirdropTool(solanaKit),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ export async function createOrcaSingleSidedWhirlpool(
|
||||
const tickUpperInitializableIndex = TickUtil.getInitializableTickIndex(tickUpperIndex, tickSpacing);
|
||||
if (!TickUtil.checkTickInBounds(tickLowerInitializableIndex) || !TickUtil.checkTickInBounds(tickUpperInitializableIndex)) throw Error('Prices out of bounds');
|
||||
const increasLiquidityQuoteParam: IncreaseLiquidityQuoteParam = {
|
||||
inputTokenAmount: BN(depositTokenAmount),
|
||||
inputTokenAmount: new BN(depositTokenAmount),
|
||||
inputTokenMint: depositTokenMint,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { createUmi, generateSigner, publicKey } from "@metaplex-foundation/umi";
|
||||
import { createCollection, ruleSet } from "@metaplex-foundation/mpl-core";
|
||||
import { mplTokenMetadata } from "@metaplex-foundation/mpl-token-metadata";
|
||||
import { generateSigner, keypairIdentity, publicKey } from "@metaplex-foundation/umi";
|
||||
import { createCollection, mplCore, ruleSet } from "@metaplex-foundation/mpl-core";
|
||||
import { CollectionOptions, CollectionDeployment } from "../types";
|
||||
import { toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { fromWeb3JsKeypair, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
|
||||
|
||||
/**
|
||||
* Deploy a new NFT collection
|
||||
@@ -17,7 +17,8 @@ export async function deploy_collection(
|
||||
): Promise<CollectionDeployment> {
|
||||
try {
|
||||
// Initialize Umi
|
||||
const umi = createUmi().use(mplTokenMetadata());
|
||||
const umi = createUmi(agent.connection.rpcEndpoint).use(mplCore());
|
||||
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
|
||||
|
||||
// Generate collection signer
|
||||
const collectionSigner = generateSigner(umi);
|
||||
@@ -27,11 +28,11 @@ export async function deploy_collection(
|
||||
address: publicKey(creator.address),
|
||||
percentage: creator.percentage,
|
||||
})) || [
|
||||
{
|
||||
address: publicKey(agent.wallet_address.toString()),
|
||||
percentage: 100,
|
||||
},
|
||||
];
|
||||
{
|
||||
address: publicKey(agent.wallet_address.toString()),
|
||||
percentage: 100,
|
||||
},
|
||||
];
|
||||
|
||||
// Create collection
|
||||
const tx = await createCollection(umi, {
|
||||
|
||||
@@ -1,57 +1,66 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
createInitializeMint2Instruction,
|
||||
MINT_SIZE,
|
||||
getMinimumBalanceForRentExemptAccount,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { Keypair, SystemProgram, Transaction } from "@solana/web3.js";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
|
||||
import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi";
|
||||
import { createFungible, mintV1, TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
|
||||
import { fromWeb3JsKeypair, fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
|
||||
/**
|
||||
* Deploy a new SPL token
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param name Name of the token
|
||||
* @param uri URI for the token metadata
|
||||
* @param symbol Symbol of the token
|
||||
* @param decimals Number of decimals for the token (default: 9)
|
||||
* @param initialSupply Initial supply to mint (optional)
|
||||
* @returns Object containing token mint address and initial account (if supply was minted)
|
||||
*/
|
||||
export async function deploy_token(
|
||||
agent: SolanaAgentKit,
|
||||
decimals: number = 9
|
||||
// initialSupply?: number
|
||||
) {
|
||||
name: string,
|
||||
uri: string,
|
||||
symbol: string,
|
||||
decimals: number = 9,
|
||||
initialSupply?: number
|
||||
): Promise<{ mint: PublicKey }> {
|
||||
try {
|
||||
// Create new token mint
|
||||
const lamports = await getMinimumBalanceForRentExemptAccount(
|
||||
agent.connection
|
||||
);
|
||||
// Create UMI instance from agent
|
||||
const umi = createUmi(agent.connection.rpcEndpoint)
|
||||
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
|
||||
|
||||
const mint = Keypair.generate();
|
||||
let account_create_ix = SystemProgram.createAccount({
|
||||
fromPubkey: agent.wallet_address,
|
||||
newAccountPubkey: mint.publicKey,
|
||||
lamports,
|
||||
space: MINT_SIZE,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
// Create new token mint
|
||||
const mint = generateSigner(umi);
|
||||
|
||||
let builder = createFungible(umi, {
|
||||
name,
|
||||
uri,
|
||||
symbol,
|
||||
sellerFeeBasisPoints: {
|
||||
basisPoints: 0n,
|
||||
identifier: "%",
|
||||
decimals: 2,
|
||||
},
|
||||
decimals,
|
||||
mint,
|
||||
});
|
||||
|
||||
let create_mint_ix = createInitializeMint2Instruction(
|
||||
mint.publicKey,
|
||||
decimals,
|
||||
agent.wallet_address,
|
||||
agent.wallet_address,
|
||||
TOKEN_PROGRAM_ID
|
||||
);
|
||||
if (initialSupply) {
|
||||
builder = builder.add(
|
||||
mintV1(umi, {
|
||||
mint: mint.publicKey,
|
||||
tokenStandard: TokenStandard.Fungible,
|
||||
tokenOwner: fromWeb3JsPublicKey(agent.wallet_address),
|
||||
amount: initialSupply,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let tx = new Transaction().add(account_create_ix, create_mint_ix);
|
||||
|
||||
let hash = await sendTx(agent, tx, [mint]);
|
||||
builder.sendAndConfirm(umi, { confirm: { commitment: 'finalized' } });
|
||||
|
||||
return {
|
||||
mint: mint.publicKey,
|
||||
mint: toWeb3JsPublicKey(mint.publicKey),
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
throw new Error(`Token deployment failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
37
src/tools/get_primary_domain.ts
Normal file
37
src/tools/get_primary_domain.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { getPrimaryDomain as _getPrimaryDomain } from "@bonfida/spl-name-service";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
/**
|
||||
* Retrieves the primary .sol domain associated with a given Solana public key.
|
||||
*
|
||||
* This function queries the Bonfida SPL Name Service to get the primary .sol domain for
|
||||
* a specified Solana public key. If the primary domain is stale or an error occurs during
|
||||
* the resolution, it throws an error.
|
||||
*
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param account The Solana public key for which to retrieve the primary domain
|
||||
* @returns A promise that resolves to the primary .sol domain as a string
|
||||
* @throws Error if the domain is stale or if the domain resolution fails
|
||||
*/
|
||||
export async function getPrimaryDomain(
|
||||
agent: SolanaAgentKit,
|
||||
account: PublicKey
|
||||
): Promise<string> {
|
||||
try {
|
||||
const { reverse, stale } = await _getPrimaryDomain(
|
||||
agent.connection,
|
||||
account
|
||||
);
|
||||
if (stale) {
|
||||
throw new Error(
|
||||
`Primary domain is stale for account: ${account.toBase58()}`
|
||||
);
|
||||
}
|
||||
return reverse;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to get primary domain for account: ${account.toBase58()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
68
src/tools/get_token_data.ts
Normal file
68
src/tools/get_token_data.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { JupiterTokenData } from "../types";
|
||||
|
||||
export async function getTokenDataByAddress(
|
||||
mint: PublicKey,
|
||||
): Promise<JupiterTokenData | undefined> {
|
||||
try {
|
||||
if (!mint) {
|
||||
throw new Error("Mint address is required");
|
||||
}
|
||||
|
||||
const response = await fetch("https://tokens.jup.ag/tokens?tags=verified", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = (await response.json()) as JupiterTokenData[];
|
||||
const token = data.find((token: JupiterTokenData) => {
|
||||
return token.address === mint.toBase58();
|
||||
});
|
||||
return token;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error fetching token data: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokenAddressFromTicker(
|
||||
ticker: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.dexscreener.com/latest/dex/search?q=${ticker}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.pairs || data.pairs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Filter for Solana pairs only and sort by FDV
|
||||
let solanaPairs = data.pairs
|
||||
.filter((pair: any) => pair.chainId === "solana")
|
||||
.sort((a: any, b: any) => (b.fdv || 0) - (a.fdv || 0));
|
||||
|
||||
solanaPairs = solanaPairs.filter(
|
||||
(pair: any) =>
|
||||
pair.baseToken.symbol.toLowerCase() === ticker.toLowerCase()
|
||||
);
|
||||
|
||||
// Return the address of the highest FDV Solana pair
|
||||
return solanaPairs[0].baseToken.address;
|
||||
} catch (error) {
|
||||
console.error("Error fetching token address from DexScreener:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokenDataByTicker(
|
||||
ticker: string
|
||||
): Promise<JupiterTokenData | undefined> {
|
||||
const address = await getTokenAddressFromTicker(ticker);
|
||||
if (!address) {
|
||||
throw new Error(`Token address not found for ticker: ${ticker}`);
|
||||
}
|
||||
return getTokenDataByAddress(new PublicKey(address));
|
||||
}
|
||||
@@ -6,9 +6,14 @@ export * from "./mint_nft";
|
||||
export * from "./transfer";
|
||||
export * from "./trade";
|
||||
export * from "./register_domain";
|
||||
export * from "./resolve_sol_domain";
|
||||
export * from "./get_primary_domain";
|
||||
export * from "./launch_pumpfun_token";
|
||||
export * from "./lend";
|
||||
export * from "./get_tps";
|
||||
export * from './stake_with_jup';
|
||||
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";
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { generateSigner } from '@metaplex-foundation/umi';
|
||||
import { create } from '@metaplex-foundation/mpl-core';
|
||||
import { generateSigner, keypairIdentity } from '@metaplex-foundation/umi';
|
||||
import { create, mplCore } from '@metaplex-foundation/mpl-core';
|
||||
import { fetchCollection } from '@metaplex-foundation/mpl-core';
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { fromWeb3JsKeypair, fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
|
||||
import { MintCollectionNFTResponse } from '../types';
|
||||
|
||||
@@ -20,7 +20,6 @@ export async function mintCollectionNFT(
|
||||
collectionMint: PublicKey,
|
||||
metadata: {
|
||||
name: string;
|
||||
symbol: string;
|
||||
uri: string;
|
||||
sellerFeeBasisPoints?: number;
|
||||
creators?: Array<{
|
||||
@@ -32,11 +31,12 @@ export async function mintCollectionNFT(
|
||||
): Promise<MintCollectionNFTResponse> {
|
||||
try {
|
||||
// Create UMI instance from agent
|
||||
const umi = createUmi(agent.connection)
|
||||
const umi = createUmi(agent.connection.rpcEndpoint).use(mplCore());
|
||||
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
|
||||
|
||||
// Convert collection mint to UMI format
|
||||
const umiCollectionMint = fromWeb3JsPublicKey(collectionMint);
|
||||
|
||||
|
||||
// Fetch the existing collection
|
||||
const collection = await fetchCollection(umi, umiCollectionMint);
|
||||
|
||||
@@ -48,8 +48,8 @@ export async function mintCollectionNFT(
|
||||
asset: assetSigner,
|
||||
collection: collection,
|
||||
name: metadata.name,
|
||||
uri: metadata.uri,
|
||||
owner: fromWeb3JsPublicKey(recipient!)
|
||||
uri: metadata.uri,
|
||||
owner: fromWeb3JsPublicKey(recipient ?? agent.wallet.publicKey)
|
||||
}).sendAndConfirm(umi);
|
||||
|
||||
return {
|
||||
|
||||
30
src/tools/resolve_sol_domain.ts
Normal file
30
src/tools/resolve_sol_domain.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { resolve } from "@bonfida/spl-name-service";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
/**
|
||||
* Resolves a .sol domain to a Solana PublicKey.
|
||||
*
|
||||
* This function uses the Bonfida SPL Name Service to resolve a given .sol domain
|
||||
* to the corresponding Solana PublicKey. The domain can be provided with or without
|
||||
* the .sol suffix.
|
||||
*
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param domain The .sol domain to resolve. This can be provided with or without the .sol TLD suffix
|
||||
* @returns A promise that resolves to the corresponding Solana PublicKey
|
||||
* @throws Error if the domain resolution fails
|
||||
*/
|
||||
export async function resolveSolDomain(
|
||||
agent: SolanaAgentKit,
|
||||
domain: string
|
||||
): Promise<PublicKey> {
|
||||
if (!domain || typeof domain !== "string") {
|
||||
throw new Error("Invalid domain. Expected a non-empty string.");
|
||||
}
|
||||
|
||||
try {
|
||||
return await resolve(agent.connection, domain);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to resolve domain: ${domain}`);
|
||||
}
|
||||
}
|
||||
306
src/tools/send_compressed_airdrop.ts
Normal file
306
src/tools/send_compressed_airdrop.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import {
|
||||
AddressLookupTableAccount,
|
||||
ComputeBudgetProgram,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent/index.js";
|
||||
import {
|
||||
buildAndSignTx,
|
||||
calculateComputeUnitPrice,
|
||||
createRpc,
|
||||
Rpc,
|
||||
sendAndConfirmTx,
|
||||
sleep,
|
||||
} from "@lightprotocol/stateless.js";
|
||||
import {
|
||||
CompressedTokenProgram,
|
||||
createTokenPool,
|
||||
} from "@lightprotocol/compressed-token";
|
||||
import { Account, getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
|
||||
|
||||
// arbitrary
|
||||
const MAX_AIRDROP_RECIPIENTS = 1000;
|
||||
const MAX_CONCURRENT_TXS = 30;
|
||||
|
||||
/**
|
||||
* Estimate the cost of an airdrop in lamports.
|
||||
* @param numberOfRecipients Number of recipients
|
||||
* @param priorityFeeInLamports Priority fee in lamports
|
||||
* @returns Estimated cost in lamports
|
||||
*/
|
||||
export const getAirdropCostEstimate = (
|
||||
numberOfRecipients: number,
|
||||
priorityFeeInLamports: number
|
||||
) => {
|
||||
const baseFee = 5000;
|
||||
const perRecipientCompressedStateFee = 300;
|
||||
|
||||
const txsNeeded = Math.ceil(numberOfRecipients / 15);
|
||||
const totalPriorityFees = txsNeeded * (baseFee + priorityFeeInLamports);
|
||||
|
||||
return (
|
||||
perRecipientCompressedStateFee * numberOfRecipients + totalPriorityFees
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send airdrop with ZK Compressed Tokens.
|
||||
* @param agent Agent
|
||||
* @param mintAddress SPL Mint address
|
||||
* @param amount Amount to send per recipient
|
||||
* @param decimals Decimals of the token
|
||||
* @param recipients Recipient wallet addresses (no ATAs)
|
||||
* @param priorityFeeInLamports Priority fee in lamports
|
||||
* @param shouldLog Whether to log progress to stdout. Defaults to false.
|
||||
*/
|
||||
export async function sendCompressedAirdrop(
|
||||
agent: SolanaAgentKit,
|
||||
mintAddress: PublicKey,
|
||||
amount: number,
|
||||
decimals: number,
|
||||
recipients: PublicKey[],
|
||||
priorityFeeInLamports: number,
|
||||
shouldLog: boolean = false
|
||||
): Promise<string[]> {
|
||||
if (recipients.length > MAX_AIRDROP_RECIPIENTS) {
|
||||
throw new Error(
|
||||
`Max airdrop can be ${MAX_AIRDROP_RECIPIENTS} recipients at a time. For more scale, use open source ZK Compression airdrop tools such as https://github.com/helius-labs/airship.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const url = agent.connection.rpcEndpoint;
|
||||
if (url.includes("devnet")) {
|
||||
throw new Error("Devnet is not supported for airdrop. Please use mainnet.");
|
||||
}
|
||||
if (!url.includes("helius")) {
|
||||
console.warn(
|
||||
"Warning: Must use RPC with ZK Compression support. Double check with your RPC provider if in doubt."
|
||||
);
|
||||
}
|
||||
|
||||
let sourceTokenAccount: Account;
|
||||
try {
|
||||
sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
|
||||
agent.connection,
|
||||
agent.wallet,
|
||||
mintAddress,
|
||||
agent.wallet.publicKey
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
"Source token account not found and failed to create it. Please add funds to your wallet and try again."
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await createTokenPool(
|
||||
agent.connection as unknown as Rpc,
|
||||
agent.wallet,
|
||||
mintAddress
|
||||
);
|
||||
} catch (error: any) {
|
||||
if (error.message.includes("already in use")) {
|
||||
// skip
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return await processAll(
|
||||
agent,
|
||||
amount * 10 ** decimals,
|
||||
mintAddress,
|
||||
recipients,
|
||||
priorityFeeInLamports,
|
||||
shouldLog
|
||||
);
|
||||
}
|
||||
|
||||
async function processAll(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
mint: PublicKey,
|
||||
recipients: PublicKey[],
|
||||
priorityFeeInLamports: number,
|
||||
shouldLog: boolean
|
||||
): Promise<string[]> {
|
||||
const mintAddress = mint;
|
||||
const payer = agent.wallet;
|
||||
|
||||
const sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
|
||||
agent.connection,
|
||||
agent.wallet,
|
||||
mintAddress,
|
||||
agent.wallet.publicKey
|
||||
);
|
||||
|
||||
const maxRecipientsPerInstruction = 5;
|
||||
const maxIxs = 3; // empirically determined (as of 12/15/2024)
|
||||
const lookupTableAddress = new PublicKey(
|
||||
"9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"
|
||||
);
|
||||
|
||||
const lookupTableAccount = (
|
||||
await agent.connection.getAddressLookupTable(lookupTableAddress)
|
||||
).value!;
|
||||
|
||||
const batches: PublicKey[][] = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < recipients.length;
|
||||
i += maxRecipientsPerInstruction * maxIxs
|
||||
) {
|
||||
batches.push(recipients.slice(i, i + maxRecipientsPerInstruction * maxIxs));
|
||||
}
|
||||
|
||||
const instructionSets = await Promise.all(
|
||||
batches.map(async (recipientBatch) => {
|
||||
const instructions: TransactionInstruction[] = [
|
||||
ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }),
|
||||
ComputeBudgetProgram.setComputeUnitPrice({
|
||||
microLamports: calculateComputeUnitPrice(
|
||||
priorityFeeInLamports,
|
||||
500_000
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
const compressIxPromises = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < recipientBatch.length;
|
||||
i += maxRecipientsPerInstruction
|
||||
) {
|
||||
const batch = recipientBatch.slice(i, i + maxRecipientsPerInstruction);
|
||||
compressIxPromises.push(
|
||||
CompressedTokenProgram.compress({
|
||||
payer: payer.publicKey,
|
||||
owner: payer.publicKey,
|
||||
source: sourceTokenAccount.address,
|
||||
toAddress: batch,
|
||||
amount: batch.map(() => amount),
|
||||
mint: mintAddress,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const compressIxs = await Promise.all(compressIxPromises);
|
||||
return [...instructions, ...compressIxs];
|
||||
})
|
||||
);
|
||||
|
||||
const url = agent.connection.rpcEndpoint;
|
||||
const rpc = createRpc(url, url, url);
|
||||
|
||||
const results = [];
|
||||
let confirmedCount = 0;
|
||||
const totalBatches = instructionSets.length;
|
||||
|
||||
const renderProgressBar = (current: number, total: number) => {
|
||||
const percentage = Math.floor((current / total) * 100);
|
||||
const filled = Math.floor((percentage / 100) * 20);
|
||||
const empty = 20 - filled;
|
||||
const bar = "█".repeat(filled) + "░".repeat(empty);
|
||||
return `Airdropped to ${Math.min(current * 15, recipients.length)}/${
|
||||
recipients.length
|
||||
} recipients [${bar}] ${percentage}%`;
|
||||
};
|
||||
|
||||
const log = (message: string) => {
|
||||
if (shouldLog && typeof process !== "undefined" && process.stdout) {
|
||||
process.stdout.write(message);
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < instructionSets.length; i += MAX_CONCURRENT_TXS) {
|
||||
const batchPromises = instructionSets
|
||||
.slice(i, i + MAX_CONCURRENT_TXS)
|
||||
.map((instructions, idx) =>
|
||||
sendTransactionWithRetry(
|
||||
rpc,
|
||||
instructions,
|
||||
payer,
|
||||
lookupTableAccount,
|
||||
i + idx
|
||||
).then((signature) => {
|
||||
confirmedCount++;
|
||||
log("\r" + renderProgressBar(confirmedCount, totalBatches));
|
||||
return signature;
|
||||
})
|
||||
);
|
||||
|
||||
const batchResults = await Promise.allSettled(batchPromises);
|
||||
results.push(...batchResults);
|
||||
}
|
||||
|
||||
log("\n");
|
||||
|
||||
const failures = results
|
||||
.filter((r) => r.status === "rejected")
|
||||
.map((r, idx) => ({
|
||||
index: idx,
|
||||
error: (r as PromiseRejectedResult).reason,
|
||||
}));
|
||||
|
||||
if (failures.length > 0) {
|
||||
throw new Error(
|
||||
`Failed to process ${failures.length} batches: ${failures
|
||||
.map((f) => f.error)
|
||||
.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
return results.map((r) => (r as PromiseFulfilledResult<string>).value);
|
||||
}
|
||||
|
||||
async function sendTransactionWithRetry(
|
||||
connection: Rpc,
|
||||
instructions: TransactionInstruction[],
|
||||
payer: Keypair,
|
||||
lookupTableAccount: AddressLookupTableAccount,
|
||||
batchIndex: number
|
||||
): Promise<string> {
|
||||
const MAX_RETRIES = 3;
|
||||
const INITIAL_BACKOFF = 500; // ms
|
||||
|
||||
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const { blockhash } = await connection.getLatestBlockhash();
|
||||
const tx = buildAndSignTx(
|
||||
instructions,
|
||||
payer,
|
||||
blockhash,
|
||||
[],
|
||||
[lookupTableAccount]
|
||||
);
|
||||
|
||||
const signature = await sendAndConfirmTx(connection, tx);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
const isRetryable =
|
||||
error.message?.includes("blockhash not found") ||
|
||||
error.message?.includes("timeout") ||
|
||||
error.message?.includes("rate limit") ||
|
||||
error.message?.includes("too many requests");
|
||||
|
||||
if (!isRetryable || attempt === MAX_RETRIES - 1) {
|
||||
throw new Error(
|
||||
`Batch ${batchIndex} failed after ${attempt + 1} attempts: ${
|
||||
error.message
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
const backoff =
|
||||
INITIAL_BACKOFF * Math.pow(2, attempt) * (0.5 + Math.random());
|
||||
await sleep(backoff);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
@@ -39,7 +39,6 @@ export interface PumpfunLaunchResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lulo Account Details response format
|
||||
*/
|
||||
@@ -55,6 +54,22 @@ export interface LuloAccountDetailsResponse {
|
||||
};
|
||||
}
|
||||
|
||||
export interface JupiterTokenData {
|
||||
address: string;
|
||||
name: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
tags: string[];
|
||||
logoURI: string;
|
||||
daily_volume: number;
|
||||
freeze_authority: string | null;
|
||||
mint_authority: string | null;
|
||||
permanent_delegate: string | null;
|
||||
extensions: {
|
||||
coingeckoId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FetchPriceResponse {
|
||||
status: "success" | "error";
|
||||
tokenId?: string;
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function getPriorityFees(connection: Connection): Promise<{
|
||||
const median =
|
||||
sortedFees.length % 2 === 0
|
||||
? ((sortedFees[mid - 1] ?? 0) + (sortedFees[mid] ?? 0)) / 2
|
||||
: (sortedFees[mid] ?? 0);
|
||||
: sortedFees[mid] ?? 0;
|
||||
|
||||
// Helper to create priority fee IX based on chosen strategy
|
||||
const createPriorityFeeIx = (fee: number) => {
|
||||
@@ -76,7 +76,7 @@ export async function getPriorityFees(connection: Connection): Promise<{
|
||||
export async function sendTx(
|
||||
agent: SolanaAgentKit,
|
||||
tx: Transaction,
|
||||
otherKeypairs?: Keypair[],
|
||||
otherKeypairs?: Keypair[]
|
||||
) {
|
||||
tx.recentBlockhash = (await agent.connection.getLatestBlockhash()).blockhash;
|
||||
tx.feePayer = agent.wallet_address;
|
||||
@@ -90,8 +90,9 @@ export async function sendTx(
|
||||
await agent.connection.confirmTransaction({
|
||||
signature: txid,
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
lastValidBlockHeight: (await agent.connection.getLatestBlockhash())
|
||||
.lastValidBlockHeight,
|
||||
lastValidBlockHeight: (
|
||||
await agent.connection.getLatestBlockhash()
|
||||
).lastValidBlockHeight,
|
||||
});
|
||||
return txid;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user