Add: tensor trade support

This commit is contained in:
Lautaro
2024-12-29 03:26:09 -03:00
parent 4f0eaffa55
commit 67dc873a04
6 changed files with 7853 additions and 0 deletions

7584
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,7 @@
"@raydium-io/raydium-sdk-v2": "0.1.95-alpha",
"@solana/spl-token": "^0.4.9",
"@solana/web3.js": "^1.95.4",
"@tensor-oss/tcomp-sdk": "^6.0.0",
"@tiplink/api": "^0.3.1",
"bn.js": "^5.2.1",
"bs58": "^6.0.0",

View File

@@ -37,6 +37,9 @@ import {
create_gibwork_task,
rock_paper_scissor,
create_TipLink,
listNFTForSale,
buyNFT,
cancelListing,
} from "../tools";
import {
CollectionDeployment,
@@ -349,4 +352,25 @@ export class SolanaAgentKit {
async createTiplink(amount: number, splmintAddress?: PublicKey) {
return create_TipLink(this, amount, splmintAddress);
}
async tensorListNFT(
nftMint: PublicKey,
price: number,
expirySeconds?: number
): Promise<string> {
return listNFTForSale(this, nftMint, price, expirySeconds);
}
async tensorBuyNFT(
nftMint: PublicKey,
maxPrice: number
): Promise<string> {
return buyNFT(this, nftMint, maxPrice);
}
async tensorCancelListing(
nftMint: PublicKey
): Promise<string> {
return cancelListing(this, nftMint);
}
}

View File

@@ -1326,6 +1326,133 @@ 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)
expirySeconds: number, expiry time in seconds (optional)`;
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,
parsedInput.expirySeconds
);
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 SolanaBuyNFTTool extends Tool {
name = "solana_buy_nft";
description = `Buy an NFT listed on Tensor Trade.
Inputs (input is a JSON string):
nftMint: string, the mint address of the NFT (required)
maxPrice: number, maximum price willing to pay in SOL (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.tensorBuyNFT(
new PublicKey(parsedInput.nftMint),
parsedInput.maxPrice
);
return JSON.stringify({
status: "success",
message: "NFT purchased successfully",
transaction: tx,
maxPrice: parsedInput.maxPrice,
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) {
return [
new SolanaBalanceTool(solanaKit),
@@ -1362,5 +1489,8 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaCreateGibworkTask(solanaKit),
new SolanaRockPaperScissorsTool(solanaKit),
new SolanaTipLinkTool(solanaKit),
new SolanaListNFTForSaleTool(solanaKit),
new SolanaBuyNFTTool(solanaKit),
new SolanaCancelNFTListingTool(solanaKit),
];
}

View File

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

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

@@ -0,0 +1,112 @@
import { SolanaAgentKit } from "../index";
import { TCompSDK } from "@tensor-oss/tcomp-sdk";
import { PublicKey, Transaction } from "@solana/web3.js";
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { BN } from "bn.js";
import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, getAccount } from '@solana/spl-token';
export async function listNFTForSale(
agent: SolanaAgentKit,
nftMint: PublicKey,
price: number,
expirySeconds?: number
): Promise<string> {
try {
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
);
console.log("Token Account:", tokenAccount);
} catch (e) {
console.error("Token account error:", e);
throw new Error(`No token account found for mint ${nftMint.toString()}`);
}
const provider = new AnchorProvider(
agent.connection,
new Wallet(agent.wallet),
AnchorProvider.defaultOptions()
);
const tcompSdk = new TCompSDK({ provider });
const priceInLamports = new BN(price * 1e9);
const expiry = expirySeconds ? new BN(expirySeconds) : null;
const { tx } = await tcompSdk.listCore({
asset: nftMint,
owner: agent.wallet_address,
amount: priceInLamports,
expireInSec: expiry
});
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 buyNFT(
agent: SolanaAgentKit,
nftMint: PublicKey,
maxPrice: number
): Promise<string> {
const provider = new AnchorProvider(
agent.connection,
new Wallet(agent.wallet),
AnchorProvider.defaultOptions()
);
const tcompSdk = new TCompSDK({ provider });
const maxPriceInLamports = new BN(maxPrice * 1e9);
const { tx } = await tcompSdk.buyCore({
asset: nftMint,
buyer: agent.wallet_address,
maxAmount: maxPriceInLamports,
owner: agent.wallet_address,
rentDest: agent.wallet_address,
payer: agent.wallet_address,
makerBroker: null
});
const transaction = new Transaction();
transaction.add(...tx.ixs);
return await agent.connection.sendTransaction(transaction, [agent.wallet, ...tx.extraSigners]);
}
export async function cancelListing(
agent: SolanaAgentKit,
nftMint: PublicKey
): Promise<string> {
const provider = new AnchorProvider(
agent.connection,
new Wallet(agent.wallet),
AnchorProvider.defaultOptions()
);
const tcompSdk = new TCompSDK({ provider });
const { tx } = await tcompSdk.delistCore({
asset: nftMint,
owner: agent.wallet_address,
rentDest: agent.wallet_address
});
const transaction = new Transaction();
transaction.add(...tx.ixs);
return await agent.connection.sendTransaction(transaction, [agent.wallet, ...tx.extraSigners]);
}