Merge branch 'main' into manifest

This commit is contained in:
DonDuala
2024-12-31 09:07:47 -04:00
committed by GitHub
20 changed files with 875 additions and 32 deletions

View File

@@ -1,3 +1,5 @@
OPENAI_API_KEY= OPENAI_API_KEY=
RPC_URL= RPC_URL=
SOLANA_PRIVATE_KEY= SOLANA_PRIVATE_KEY=
JUPITER_REFERRAL_ACCOUNT=
JUPITER_FEE_BPS=

View File

@@ -47,8 +47,8 @@ Anyone - whether an SF-based AI researcher or a crypto-native builder - can brin
- Launch on Pump via PumpPortal - Launch on Pump via PumpPortal
- Raydium pool creation (CPMM, CLMM, AMMv4) - Raydium pool creation (CPMM, CLMM, AMMv4)
- Orca Whirlpool integration - Orca Whirlpool integration
- Meteora Dynamic AMM, DLMM Pool, and Alpga Vault
- Manifest market creation, and limit orders - Manifest market creation, and limit orders
- Meteora Dynamic AMM, DLMM Pool, and Alpha Vault
- Openbook market creation - Openbook market creation
- Register and Resolve SNS - Register and Resolve SNS
- Jito Bundles - Jito Bundles
@@ -56,7 +56,7 @@ Anyone - whether an SF-based AI researcher or a crypto-native builder - can brin
- Register/resolve Alldomains - Register/resolve Alldomains
- **Solana Blinks** - **Solana Blinks**
- Lending by Lulon (Best APR for USDC) - Lending by Lulo (Best APR for USDC)
- Send Arcade Games - Send Arcade Games
- JupSOL staking - JupSOL staking

View File

@@ -15,7 +15,7 @@ To use this feature, ensure you have the following:
1. **PostgreSQL Database URL**: Create and host ur PostgreSQL databse and enter the URL. It will be of the format "postgresql://user:password@localhost:5432/db" 1. **PostgreSQL Database URL**: Create and host ur PostgreSQL databse and enter the URL. It will be of the format "postgresql://user:password@localhost:5432/db"
## Before applying persistance ## Without persistence
``` ```
Available modes: Available modes:
1. chat 1. chat
@@ -27,7 +27,7 @@ Starting chat mode... Type 'exit' to end.
Prompt: i am arpit Prompt: i am arpit
Hello Arpit! How can I assist you today? Hello Arpit! How can I assist you today?
Prompt: ^С Prompt: ^С
® arpitsingh Mac persistance-agent & ts-node index.ts ® arpitsingh Mac persistent-agent & ts-node index.ts
Starting Agent... Starting Agent...
Available modes: Available modes:
1. chat 1. chat
@@ -39,7 +39,7 @@ Starting chat mode... Type 'exit' to end.
Prompt: do u know my name Prompt: do u know my name
I don't know your name yet. If you'd like, you can share it. I don't know your name yet. If you'd like, you can share it.
``` ```
## After applying persistence ## With persistence
``` ```
Available modes: Available modes:
1. chat 1. chat
@@ -51,7 +51,7 @@ Starting chat mode... Type 'exit' to end.
Prompt: i am arpit Prompt: i am arpit
Hello Arpit! How can I assist you today? Hello Arpit! How can I assist you today?
Prompt: ^С Prompt: ^С
® arpitsingh Mac persistance-agent & ts-node index.ts ® arpitsingh Mac persistent-agent & ts-node index.ts
Starting Agent... Starting Agent...
Available modes: Available modes:
1. chat 1. chat

View File

@@ -1,8 +1,10 @@
# Telegram Bot Starter with Solana Agent Kit # Telegram Bot Starter with Solana Agent Kit
This example showcases how we can make a telegram bot with the Solana Agent Kit by Send AI. This example showcases how we can make a telegram bot with the Solana Agent Kit by Send AI.
## Quick Deploy
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsendaifun%2Fsolana-agent-kit%2Ftree%2Fmain%2Fexamples%2Ftg-bot-starter&env=OPENAI_API_KEY,RPC_URL,SOLANA_PRIVATE_KEY,TELEGRAM_BOT_TOKEN&project-name=solana-agent-kit&repository-name=sak-yourprojectname)
## How to get the telegram bot token ## How to get the telegram bot token
You can check [here](https://help.zoho.com/portal/en/kb/desk/support-channels/instant-messaging/telegram/articles/telegram-integration-with-zoho-desk#How_to_find_a_token_for_an_existing_Telegram_Bot) how you can obtain a bot token for your telegram bot. You can check [here](https://help.zoho.com/portal/en/kb/desk/support-channels/instant-messaging/telegram/articles/telegram-integration-with-zoho-desk#How_to_find_a_token_for_an_existing_Telegram_Bot) how you can obtain a bot token for your telegram bot.
@@ -18,5 +20,5 @@ You can check [here](https://help.zoho.com/portal/en/kb/desk/support-channels/in
- You can host it on Vercel too as we have used NextJs in this. - You can host it on Vercel too as we have used NextJs in this.
- Once the URL is set successfully, you will see this ``` {"ok":true,"result":true,"description":"Webhook was set"} ``` - Once the URL is set successfully, you will see this ``` {"ok":true,"result":true,"description":"Webhook was set"} ```
Done!!! Congrtulations you just hosted Solana Agent Kit on a Telegram bot. Done!!! Congratulations you just hosted Solana Agent Kit on a Telegram bot.

View File

@@ -42,6 +42,7 @@
"@pythnetwork/price-service-client": "^1.9.0", "@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",
"@tensor-oss/tensorswap-sdk": "^4.5.0",
"@solana/web3.js": "^1.98.0", "@solana/web3.js": "^1.98.0",
"@tiplink/api": "^0.3.1", "@tiplink/api": "^0.3.1",
"bn.js": "^5.2.1", "bn.js": "^5.2.1",

603
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import bs58 from "bs58"; import bs58 from "bs58";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
import { DEFAULT_OPTIONS } from "../constants"; import { DEFAULT_OPTIONS } from "../constants";
import { Config } from "../types";
import { import {
deploy_collection, deploy_collection,
deploy_token, deploy_token,
@@ -47,6 +48,8 @@ import {
orcaFetchPositions, orcaFetchPositions,
rock_paper_scissor, rock_paper_scissor,
create_TipLink, create_TipLink,
listNFTForSale,
cancelListing,
} from "../tools"; } from "../tools";
import { import {
@@ -73,17 +76,17 @@ export class SolanaAgentKit {
public connection: Connection; public connection: Connection;
public wallet: Keypair; public wallet: Keypair;
public wallet_address: PublicKey; public wallet_address: PublicKey;
public openai_api_key: string | null; public config: Config;
constructor( constructor(
private_key: string, private_key: string,
rpc_url = "https://api.mainnet-beta.solana.com", rpc_url = "https://api.mainnet-beta.solana.com",
openai_api_key: string | null = null, config: Config,
) { ) {
this.connection = new Connection(rpc_url); this.connection = new Connection(rpc_url);
this.wallet = Keypair.fromSecretKey(bs58.decode(private_key)); this.wallet = Keypair.fromSecretKey(bs58.decode(private_key));
this.wallet_address = this.wallet.publicKey; this.wallet_address = this.wallet.publicKey;
this.openai_api_key = openai_api_key; this.config = config;
} }
// Tool methods // Tool methods
@@ -316,8 +319,7 @@ export class SolanaAgentKit {
return getOwnedDomainsForTLD(this, tld); return getOwnedDomainsForTLD(this, tld);
} }
// eslint-disable-next-line @typescript-eslint/ban-types async getAllDomainsTLDs(): Promise<string[]> {
async getAllDomainsTLDs(): Promise<String[]> {
return getAllDomainsTLDs(this); return getAllDomainsTLDs(this);
} }
@@ -440,4 +442,12 @@ export class SolanaAgentKit {
async createTiplink(amount: number, splmintAddress?: PublicKey) { async createTiplink(amount: number, splmintAddress?: PublicKey) {
return create_TipLink(this, amount, splmintAddress); return create_TipLink(this, amount, splmintAddress);
} }
async tensorListNFT(nftMint: PublicKey, price: number): Promise<string> {
return listNFTForSale(this, nftMint, price);
}
async tensorCancelListing(nftMint: PublicKey): Promise<string> {
return cancelListing(this, nftMint);
}
} }

View File

@@ -22,9 +22,12 @@ export const TOKENS = {
export const DEFAULT_OPTIONS = { export const DEFAULT_OPTIONS = {
SLIPPAGE_BPS: 300, SLIPPAGE_BPS: 300,
TOKEN_DECIMALS: 9, TOKEN_DECIMALS: 9,
RERERRAL_FEE: 200,
} as const; } as const;
/** /**
* Jupiter API URL * Jupiter API URL
*/ */
export const JUP_API = "https://quote-api.jup.ag/v6"; export const JUP_API = "https://quote-api.jup.ag/v6";
export const JUP_REFERRAL_ADDRESS =
"REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3";

View File

@@ -46,7 +46,7 @@ export class SolanaBalanceTool extends Tool {
export class SolanaBalanceOtherTool extends Tool { export class SolanaBalanceOtherTool extends Tool {
name = "solana_balance_other"; name = "solana_balance_other";
description = `Get the balance of a Solana wallet or token account different from the agent's wallet. description = `Get the balance of a Solana wallet or token account which is different from the agent's wallet.
If no tokenAddress is provided, the SOL balance of the wallet will be returned. If no tokenAddress is provided, the SOL balance of the wallet will be returned.
@@ -904,7 +904,7 @@ export class SolanaCompressedAirdropTool extends Tool {
} }
} }
export class SolanaClosePostition extends Tool { export class SolanaClosePosition extends Tool {
name = "orca_close_position"; name = "orca_close_position";
description = `Closes an existing liquidity position in an Orca Whirlpool. This function fetches the position description = `Closes an existing liquidity position in an Orca Whirlpool. This function fetches the position
details using the provided mint address and closes the position with a 1% slippage. details using the provided mint address and closes the position with a 1% slippage.
@@ -1186,7 +1186,7 @@ export class SolanaOrcaOpenSingleSidedPosition extends Tool {
export class SolanaRaydiumCreateAmmV4 extends Tool { export class SolanaRaydiumCreateAmmV4 extends Tool {
name = "raydium_create_ammV4"; name = "raydium_create_ammV4";
description = `Raydium's Legacy AMM that requiers an OpenBook marketID description = `Raydium's Legacy AMM that requires an OpenBook marketID
Inputs (input is a json string): Inputs (input is a json string):
marketId: string (required) marketId: string (required)
@@ -1212,7 +1212,7 @@ export class SolanaRaydiumCreateAmmV4 extends Tool {
return JSON.stringify({ return JSON.stringify({
status: "success", status: "success",
message: "Create raydium amm v4 pool successfully", message: "Raydium amm v4 pool created successfully",
transaction: tx, transaction: tx,
}); });
} catch (error: any) { } catch (error: any) {
@@ -1257,7 +1257,7 @@ export class SolanaRaydiumCreateClmm extends Tool {
return JSON.stringify({ return JSON.stringify({
status: "success", status: "success",
message: "Create raydium clmm pool successfully", message: "Raydium clmm pool created successfully",
transaction: tx, transaction: tx,
}); });
} catch (error: any) { } catch (error: any) {
@@ -1305,7 +1305,7 @@ export class SolanaRaydiumCreateCpmm extends Tool {
return JSON.stringify({ return JSON.stringify({
status: "success", status: "success",
message: "Create raydium cpmm pool successfully", message: "Raydium cpmm pool created successfully",
transaction: tx, transaction: tx,
}); });
} catch (error: any) { } catch (error: any) {
@@ -1347,7 +1347,7 @@ export class SolanaOpenbookCreateMarket extends Tool {
return JSON.stringify({ return JSON.stringify({
status: "success", status: "success",
message: "Create openbook market successfully", message: "Openbook market created successfully",
transaction: tx, transaction: tx,
}); });
} catch (error: any) { } catch (error: any) {
@@ -1736,6 +1736,95 @@ export class SolanaTipLinkTool extends Tool {
} }
} }
export class SolanaListNFTForSaleTool extends Tool {
name = "solana_list_nft_for_sale";
description = `List an NFT for sale on Tensor Trade.
Inputs (input is a JSON string):
nftMint: string, the mint address of the NFT (required)
price: number, price in SOL (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
// Validate NFT ownership first
const nftAccount =
await this.solanaKit.connection.getTokenAccountsByOwner(
this.solanaKit.wallet_address,
{ mint: new PublicKey(parsedInput.nftMint) },
);
if (nftAccount.value.length === 0) {
return JSON.stringify({
status: "error",
message:
"NFT not found in wallet. Please make sure you own this NFT.",
code: "NFT_NOT_FOUND",
});
}
const tx = await this.solanaKit.tensorListNFT(
new PublicKey(parsedInput.nftMint),
parsedInput.price,
);
return JSON.stringify({
status: "success",
message: "NFT listed for sale successfully",
transaction: tx,
price: parsedInput.price,
nftMint: parsedInput.nftMint,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}
export class SolanaCancelNFTListingTool extends Tool {
name = "solana_cancel_nft_listing";
description = `Cancel an NFT listing on Tensor Trade.
Inputs (input is a JSON string):
nftMint: string, the mint address of the NFT (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.tensorCancelListing(
new PublicKey(parsedInput.nftMint),
);
return JSON.stringify({
status: "success",
message: "NFT listing cancelled successfully",
transaction: tx,
nftMint: parsedInput.nftMint,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}
export function createSolanaTools(solanaKit: SolanaAgentKit) { export function createSolanaTools(solanaKit: SolanaAgentKit) {
return [ return [
new SolanaBalanceTool(solanaKit), new SolanaBalanceTool(solanaKit),
@@ -1782,5 +1871,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaCreateGibworkTask(solanaKit), new SolanaCreateGibworkTask(solanaKit),
new SolanaRockPaperScissorsTool(solanaKit), new SolanaRockPaperScissorsTool(solanaKit),
new SolanaTipLinkTool(solanaKit), new SolanaTipLinkTool(solanaKit),
new SolanaListNFTForSaleTool(solanaKit),
new SolanaCancelNFTListingTool(solanaKit),
]; ];
} }

View File

@@ -16,12 +16,12 @@ export async function create_image(
n: number = 1, n: number = 1,
) { ) {
try { try {
if (!agent.openai_api_key) { if (!agent.config.OPENAI_API_KEY) {
throw new Error("OpenAI API key not found in agent configuration"); throw new Error("OpenAI API key not found in agent configuration");
} }
const openai = new OpenAI({ const openai = new OpenAI({
apiKey: agent.openai_api_key, apiKey: agent.config.OPENAI_API_KEY,
}); });
const response = await openai.images.generate({ const response = await openai.images.generate({

View File

@@ -8,11 +8,10 @@ import { getAllTld } from "@onsol/tldparser";
*/ */
export async function getAllDomainsTLDs( export async function getAllDomainsTLDs(
agent: SolanaAgentKit, agent: SolanaAgentKit,
// eslint-disable-next-line @typescript-eslint/ban-types ): Promise<string[]> {
): Promise<String[]> {
try { try {
const tlds = await getAllTld(agent.connection); const tlds = await getAllTld(agent.connection);
return tlds.map((tld) => tld.tld); return tlds.map((tld) => String(tld.tld));
} catch (error: any) { } catch (error: any) {
throw new Error(`Failed to fetch TLDs: ${error.message}`); throw new Error(`Failed to fetch TLDs: ${error.message}`);
} }

View File

@@ -50,3 +50,5 @@ export * from "./create_gibwork_task";
export * from "./rock_paper_scissor"; export * from "./rock_paper_scissor";
export * from "./create_tiplinks"; export * from "./create_tiplinks";
export * from "./tensor_trade";

108
src/tools/tensor_trade.ts Normal file
View File

@@ -0,0 +1,108 @@
import { SolanaAgentKit } from "../index";
import { TensorSwapSDK } from "@tensor-oss/tensorswap-sdk";
import { PublicKey, Transaction } from "@solana/web3.js";
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { BN } from "bn.js";
import {
getAssociatedTokenAddress,
TOKEN_PROGRAM_ID,
getAccount,
} from "@solana/spl-token";
export async function listNFTForSale(
agent: SolanaAgentKit,
nftMint: PublicKey,
price: number,
): Promise<string> {
try {
if (!PublicKey.isOnCurve(nftMint)) {
throw new Error("Invalid NFT mint address");
}
const mintInfo = await agent.connection.getAccountInfo(nftMint);
if (!mintInfo) {
throw new Error(`NFT mint ${nftMint.toString()} does not exist`);
}
const ata = await getAssociatedTokenAddress(nftMint, agent.wallet_address);
try {
const tokenAccount = await getAccount(agent.connection, ata);
if (!tokenAccount || tokenAccount.amount <= 0) {
throw new Error(`You don't own this NFT (${nftMint.toString()})`);
}
} catch (error: any) {
throw new Error(
`No token account found for mint ${nftMint.toString()}. Make sure you own this NFT.`,
);
}
const provider = new AnchorProvider(
agent.connection,
new Wallet(agent.wallet),
AnchorProvider.defaultOptions(),
);
const tensorSwapSdk = new TensorSwapSDK({ provider });
const priceInLamports = new BN(price * 1e9);
const nftSource = await getAssociatedTokenAddress(
nftMint,
agent.wallet_address,
);
const { tx } = await tensorSwapSdk.list({
nftMint,
nftSource,
owner: agent.wallet_address,
price: priceInLamports,
tokenProgram: TOKEN_PROGRAM_ID,
payer: agent.wallet_address,
});
const transaction = new Transaction();
transaction.add(...tx.ixs);
return await agent.connection.sendTransaction(transaction, [
agent.wallet,
...tx.extraSigners,
]);
} catch (error: any) {
console.error("Full error details:", error);
throw error;
}
}
export async function cancelListing(
agent: SolanaAgentKit,
nftMint: PublicKey,
): Promise<string> {
const provider = new AnchorProvider(
agent.connection,
new Wallet(agent.wallet),
AnchorProvider.defaultOptions(),
);
const tensorSwapSdk = new TensorSwapSDK({ provider });
const nftDest = await getAssociatedTokenAddress(
nftMint,
agent.wallet_address,
false,
TOKEN_PROGRAM_ID,
);
const { tx } = await tensorSwapSdk.delist({
nftMint,
nftDest,
owner: agent.wallet_address,
tokenProgram: TOKEN_PROGRAM_ID,
payer: agent.wallet_address,
authData: null,
});
const transaction = new Transaction();
transaction.add(...tx.ixs);
return await agent.connection.sendTransaction(transaction, [
agent.wallet,
...tx.extraSigners,
]);
}

View File

@@ -1,6 +1,11 @@
import { VersionedTransaction, PublicKey } from "@solana/web3.js"; import { VersionedTransaction, PublicKey } from "@solana/web3.js";
import { SolanaAgentKit } from "../index"; import { SolanaAgentKit } from "../index";
import { TOKENS, DEFAULT_OPTIONS, JUP_API } from "../constants"; import {
TOKENS,
DEFAULT_OPTIONS,
JUP_API,
JUP_REFERRAL_ADDRESS,
} from "../constants";
import { getMint } from "@solana/spl-token"; import { getMint } from "@solana/spl-token";
/** /**
* Swap tokens using Jupiter Exchange * Swap tokens using Jupiter Exchange
@@ -11,6 +16,7 @@ import { getMint } from "@solana/spl-token";
* @param slippageBps Slippage tolerance in basis points (default: 300 = 3%) * @param slippageBps Slippage tolerance in basis points (default: 300 = 3%)
* @returns Transaction signature * @returns Transaction signature
*/ */
export async function trade( export async function trade(
agent: SolanaAgentKit, agent: SolanaAgentKit,
outputMint: PublicKey, outputMint: PublicKey,
@@ -38,11 +44,24 @@ export async function trade(
`&amount=${scaledAmount}` + `&amount=${scaledAmount}` +
`&slippageBps=${slippageBps}` + `&slippageBps=${slippageBps}` +
`&onlyDirectRoutes=true` + `&onlyDirectRoutes=true` +
`&maxAccounts=20`, `&maxAccounts=20` +
`${agent.config.JUPITER_FEE_BPS ? `&platformFeeBps=${agent.config.JUPITER_FEE_BPS}` : ""}`,
) )
).json(); ).json();
// Get serialized transaction // Get serialized transaction
let feeAccount;
if (agent.config.JUPITER_REFERRAL_ACCOUNT) {
[feeAccount] = PublicKey.findProgramAddressSync(
[
Buffer.from("referral_ata"),
new PublicKey(agent.config.JUPITER_REFERRAL_ACCOUNT).toBuffer(),
TOKENS.SOL.toBuffer(),
],
new PublicKey(JUP_REFERRAL_ADDRESS),
);
}
const { swapTransaction } = await ( const { swapTransaction } = await (
await fetch("https://quote-api.jup.ag/v6/swap", { await fetch("https://quote-api.jup.ag/v6/swap", {
method: "POST", method: "POST",
@@ -55,6 +74,7 @@ export async function trade(
wrapAndUnwrapSol: true, wrapAndUnwrapSol: true,
dynamicComputeUnitLimit: true, dynamicComputeUnitLimit: true,
prioritizationFeeLamports: "auto", prioritizationFeeLamports: "auto",
feeAccount: feeAccount ? feeAccount.toString() : null,
}), }),
}) })
).json(); ).json();

View File

@@ -1,5 +1,11 @@
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
export interface Config {
OPENAI_API_KEY?: string;
JUPITER_REFERRAL_ACCOUNT?: string;
JUPITER_FEE_BPS?: number;
}
export interface Creator { export interface Creator {
address: string; address: string;
percentage: number; percentage: number;

View File

@@ -53,7 +53,9 @@ async function initializeAgent() {
const solanaAgent = new SolanaAgentKit( const solanaAgent = new SolanaAgentKit(
process.env.SOLANA_PRIVATE_KEY!, process.env.SOLANA_PRIVATE_KEY!,
process.env.RPC_URL, process.env.RPC_URL,
process.env.OPENAI_API_KEY!, {
OPENAI_API_KEY: process.env.OPENAI_API_KEY!,
},
); );
const tools = createSolanaTools(solanaAgent); const tools = createSolanaTools(solanaAgent);