Merge pull request #12 from blockiosaurus/main

Updating NFTs to Core and adding token Metadata
This commit is contained in:
ARYAN
2024-12-19 18:41:29 +05:30
committed by GitHub
10 changed files with 401 additions and 465 deletions

View File

@@ -1,3 +1,3 @@
OPENAI_API_KEY=
HELIUS_API_KEY=
SOLANA_PRIVATE_KEY=
RPC_URL=
SOLANA_PRIVATE_KEY=

View File

@@ -44,9 +44,9 @@ import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";
// Initialize with private key and optional RPC URL
const agent = new SolanaAgentKit(
"your-wallet-private-key-as-base58",
"https://api.mainnet-beta.solana.com",
"your-openai-api-key"
"your-wallet-private-key-as-base58",
"https://api.mainnet-beta.solana.com",
"your-openai-api-key"
);
// Create LangChain tools
@@ -61,9 +61,9 @@ const tools = createSolanaTools(agent);
import { deploy_token } from "solana-agent-kit";
const result = await deploy_token(
agent,
9, // decimals
1000000 // initial supply
agent,
9, // decimals
1000000 // initial supply
);
console.log("Token Mint Address:", result.mint.toString());
@@ -75,15 +75,15 @@ console.log("Token Mint Address:", result.mint.toString());
import { deploy_collection } from "solana-agent-kit";
const collection = await deploy_collection(agent, {
name: "My NFT Collection",
uri: "https://arweave.net/metadata.json",
royaltyBasisPoints: 500, // 5%
creators: [
{
address: "creator-wallet-address",
percentage: 100,
},
],
name: "My NFT Collection",
uri: "https://arweave.net/metadata.json",
royaltyBasisPoints: 500, // 5%
creators: [
{
address: "creator-wallet-address",
percentage: 100,
},
],
});
```
@@ -94,11 +94,11 @@ import { trade } from "solana-agent-kit";
import { PublicKey } from "@solana/web3.js";
const signature = await trade(
agent,
new PublicKey("target-token-mint"),
100, // amount
new PublicKey("source-token-mint"),
300 // 3% slippage
agent,
new PublicKey("target-token-mint"),
100, // amount
new PublicKey("source-token-mint"),
300 // 3% slippage
);
```
@@ -109,8 +109,8 @@ import { lendAsset } from "solana-agent-kit";
import { PublicKey } from "@solana/web3.js";
const signature = await lendAsset(
agent,
100 // amount
agent,
100 // amount
);
```
@@ -120,8 +120,8 @@ const signature = await lendAsset(
import { stakeWithJup } from "solana-agent-kit";
const signature = await stakeWithJup(
agent,
1 // amount in SOL
agent,
1 // amount in SOL
);
```
@@ -131,8 +131,8 @@ const signature = await stakeWithJup(
import { fetchPrice } from "solana-agent-kit";
const price = await fetchPrice(
agent,
"JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" // Token mint address
agent,
"JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" // Token mint address
);
console.log("Price in USDC:", price);
@@ -142,9 +142,9 @@ console.log("Price in USDC:", price);
### Core Functions
#### `deploy_token(agent, decimals?, initialSupply?)`
#### `deploy_token(agent, decimals?, name, uri, symbol, initialSupply?)`
Deploy a new SPL token with optional initial supply.
Deploy a new SPL token with optional initial supply. If not specified, decimals default to 9.
#### `deploy_collection(agent, options)`
@@ -181,6 +181,7 @@ The toolkit relies on several key Solana and Metaplex libraries:
- @solana/web3.js
- @solana/spl-token
- @metaplex-foundation/mpl-token-metadata
- @metaplex-foundation/mpl-core
- @metaplex-foundation/umi
## Contributing

View File

@@ -37,4 +37,4 @@
"@types/node": "^22.9.0",
"ts-node": "^10.9.2"
}
}
}

508
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -53,10 +53,13 @@ export class SolanaAgentKit {
}
async deployToken(
decimals: number = DEFAULT_OPTIONS.TOKEN_DECIMALS
// initialSupply?: number
name: string,
uri: string,
symbol: string,
decimals: number = DEFAULT_OPTIONS.TOKEN_DECIMALS,
initialSupply?: number,
) {
return deploy_token(this, decimals);
return deploy_token(this, name, uri, symbol, decimals, initialSupply);
}
async deployCollection(options: CollectionOptions) {
@@ -83,11 +86,11 @@ export class SolanaAgentKit {
return registerDomain(this, name, spaceKB);
}
async resolveSolDomain(domain:string ){
async resolveSolDomain(domain: string) {
return resolveSolDomain(this, domain)
}
async getPrimaryDomain(account: PublicKey){
async getPrimaryDomain(account: PublicKey) {
return getPrimaryDomain(this, account)
}
@@ -132,7 +135,7 @@ export class SolanaAgentKit {
options
);
}
async stake(
amount: number,
) {

View File

@@ -64,7 +64,7 @@ export class SolanaTransferTool extends Tool {
const tx = await this.solanaKit.transfer(
recipient,
parsedInput.amount,
mintAddress,
mintAddress
);
return JSON.stringify({
@@ -87,38 +87,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",
@@ -138,57 +130,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);
@@ -210,52 +165,43 @@ 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,
{
name: parsedInput.name,
uri: parsedInput.uri,
},
parsedInput.recipient
? new PublicKey(parsedInput.recipient)
: undefined,
: 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) {
@@ -292,7 +238,7 @@ export class SolanaTradeTool extends Tool {
parsedInput.inputMint
? new PublicKey(parsedInput.inputMint)
: new PublicKey("So11111111111111111111111111111111111111112"),
parsedInput.slippageBps,
parsedInput.slippageBps
);
return JSON.stringify({
@@ -372,7 +318,7 @@ export class SolanaRegisterDomainTool extends Tool {
const tx = await this.solanaKit.registerDomain(
parsedInput.name,
parsedInput.spaceKB || 1,
parsedInput.spaceKB || 1
);
return JSON.stringify({
@@ -424,7 +370,6 @@ export class SolanaResolveDomainTool extends Tool {
}
}
export class SolanaGetDomainTool extends Tool {
name = "solana_get_domain";
description = `Retrieve the .sol domain associated for a given account address.
@@ -437,12 +382,11 @@ export class SolanaGetDomainTool extends Tool {
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",
@@ -529,7 +473,7 @@ export class SolanaPumpfunTokenLaunchTool extends Tool {
telegram: parsedInput.telegram,
website: parsedInput.website,
initialLiquiditySOL: parsedInput.initialLiquiditySOL,
},
}
);
return JSON.stringify({
@@ -714,9 +658,7 @@ export class SolanaTokenDataTool extends Tool {
try {
const parsedInput = input.trim();
const tokenData = await this.solanaKit.getTokenDataByAddress(
parsedInput
);
const tokenData = await this.solanaKit.getTokenDataByAddress(parsedInput);
return JSON.stringify({
status: "success",

View File

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

View File

@@ -1,54 +1,69 @@
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,
})
);
}
builder.sendAndConfirm(umi, { confirm: { commitment: 'finalized' } });
console.log(
"Token deployed successfully. Mint address: ",
mint.publicKey.toString()
);
let tx = new Transaction().add(account_create_ix, create_mint_ix);
let hash = await sendTx(agent, tx, [mint]);
return {
mint: mint.publicKey,
mint: toWeb3JsPublicKey(mint.publicKey),
};
} catch (error: any) {
console.log(error);

View File

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

View File

@@ -12,8 +12,8 @@ dotenv.config();
function validateEnvironment(): void {
const missingVars: string[] = [];
const requiredVars = ["OPENAI_API_KEY", "HELIUS_API_KEY", "SOLANA_PRIVATE_KEY"];
const requiredVars = ["OPENAI_API_KEY", "RPC_URL", "SOLANA_PRIVATE_KEY"];
requiredVars.forEach(varName => {
if (!process.env[varName]) {
missingVars.push(varName);
@@ -52,7 +52,7 @@ async function initializeAgent() {
const solanaKit = new SolanaAgentKit(
process.env.SOLANA_PRIVATE_KEY!,
`https://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}`,
process.env.RPC_URL,
process.env.OPENAI_API_KEY!
);
@@ -176,7 +176,7 @@ async function chooseMode(): Promise<"chat" | "auto"> {
.trim();
rl.close();
if (choice === "1" || choice === "chat") {
return "chat";
} else if (choice === "2" || choice === "auto") {