Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tiny-apes-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/solana": patch
---

Optimize Drop creation flow, cleanup types
17 changes: 8 additions & 9 deletions packages/solana/src/classes/claim-conditions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { TransactionResult } from "../types/common";
import {
NFTDropClaimInput,
NFTDropClaimSchema,
NFTDropClaimOutput,
NFTDropConditionsOutputSchema,
NFTDropMetadataInput,
} from "../types/contracts/nft-drop";
import { Metaplex } from "@metaplex-foundation/js";
import { PublicKey } from "@solana/web3.js";
Expand All @@ -16,21 +15,21 @@ export class ClaimConditions {
this.metaplex = metaplex;
}

async get(): Promise<NFTDropClaimOutput> {
async get(): Promise<NFTDropMetadataInput> {
const candyMachine = await this.getCandyMachine();

return {
price: BigInt(candyMachine.price.basisPoints.toNumber()),
sellerFeeBasisPoints: BigInt(candyMachine.sellerFeeBasisPoints),
itemsAvailable: BigInt(candyMachine.itemsAvailable.toNumber()),
price: candyMachine.price.basisPoints.toNumber(),
sellerFeeBasisPoints: candyMachine.sellerFeeBasisPoints,
itemsAvailable: candyMachine.itemsAvailable.toNumber(),
goLiveDate: candyMachine.goLiveDate
? new Date(candyMachine.goLiveDate.toNumber() * 1000)
: undefined,
};
}

async set(metadata: NFTDropClaimInput): Promise<TransactionResult> {
const parsed = NFTDropClaimSchema.parse(metadata);
async set(metadata: NFTDropMetadataInput): Promise<TransactionResult> {
const parsed = NFTDropConditionsOutputSchema.parse(metadata);

const result = await this.metaplex
.candyMachines()
Expand Down
62 changes: 49 additions & 13 deletions packages/solana/src/classes/deployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import {
TokenMetadataInputSchema,
} from "../types/contracts";
import {
NFTDropContractSchema,
NFTDropConditionsOutputSchema,
NFTDropMetadataInput,
} from "../types/contracts/nft-drop";
import { enforceCreator } from "./helpers/creators-helper";
import { findMetadataPda, Metaplex, token } from "@metaplex-foundation/js";
import {
findMetadataPda,
Metaplex,
sol,
toBigNumber,
token,
} from "@metaplex-foundation/js";
import {
createCreateMetadataAccountV2Instruction,
DataV2,
} from "@metaplex-foundation/mpl-token-metadata";
import { Keypair, PublicKey } from "@solana/web3.js";
import { Keypair } from "@solana/web3.js";
import { IStorage } from "@thirdweb-dev/storage";
import BN from "bn.js";

export class Deployer {
private metaplex: Metaplex;
Expand Down Expand Up @@ -94,23 +101,52 @@ export class Deployer {
}

async createNftDrop(metadata: NFTDropMetadataInput): Promise<string> {
const parsed = NFTDropContractSchema.parse(metadata);
const collectionInfo = NFTCollectionMetadataInputSchema.parse(metadata);
const candyMachineInfo = NFTDropConditionsOutputSchema.parse(metadata);
const uri = await this.storage.uploadMetadata(collectionInfo);

// TODO make it a single tx
const collection = await this.createNftCollection(metadata);
const collectionMint = Keypair.generate();
const collectionTx = await this.metaplex
.nfts()
.builders()
.create({
useNewMint: collectionMint,
name: collectionInfo.name,
symbol: collectionInfo.symbol,
sellerFeeBasisPoints: 0,
uri,
isCollection: true,
creators: enforceCreator(
collectionInfo.creators,
this.metaplex.identity().publicKey,
),
});

const { candyMachine: nftDrop } = await this.metaplex
const candyMachineKeypair = Keypair.generate();
const candyMachineTx = await this.metaplex
.candyMachines()
.builders()
.create({
...parsed,
collection: new PublicKey(collection),
...candyMachineInfo,
price: candyMachineInfo.price || sol(0),
sellerFeeBasisPoints: candyMachineInfo.sellerFeeBasisPoints || 0,
itemsAvailable: candyMachineInfo.itemsAvailable || toBigNumber(0),
candyMachine: candyMachineKeypair,
collection: collectionMint.publicKey,
creators: enforceCreator(
parsed.creators,
collectionInfo.creators,
this.metaplex.identity().publicKey,
),
})
.run();
});

const result = await collectionTx
.add(candyMachineTx)
.sendAndConfirm(this.metaplex);

if (!result.response.signature) {
throw new Error("Transaction failed");
}

return nftDrop.address.toBase58();
return candyMachineKeypair.publicKey.toBase58();
}
}
56 changes: 27 additions & 29 deletions packages/solana/src/classes/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
CandyMachine,
Metadata,
Metaplex,
TokenMetadataProgram,
TokenProgram,
} from "@metaplex-foundation/js";
import { TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
import { PublicKey } from "@solana/web3.js";
Expand All @@ -17,48 +17,46 @@ export class Registry {

public async getAccountType(address: string) {
try {
const metadata = await this.metaplex
.nfts()
.findByMint({ mintAddress: new PublicKey(address) })
const candyMachine = await this.metaplex
.candyMachines()
.findByAddress({ address: new PublicKey(address) })
.run();
if (metadata) {
if (metadata.collectionDetails) {
return "nft-collection";
} else {
if (metadata.tokenStandard === TokenStandard.Fungible) {
return "token";
}
}
if (candyMachine) {
return "nft-drop";
}
} catch (e) {
try {
const candyMachine = await this.metaplex
.candyMachines()
.findByAddress({ address: new PublicKey(address) })
.run();
if (candyMachine) {
return "nft-drop";
} catch (err) {
// ignore and try next
}
const metadata = await this.metaplex
.nfts()
.findByMint({ mintAddress: new PublicKey(address) })
.run();

if (metadata) {
if (metadata.collectionDetails) {
return "nft-collection";
} else {
if (metadata.tokenStandard === TokenStandard.Fungible) {
return "token";
}
} catch (err) {
return undefined;
}
}
return undefined;
throw new Error("Unknown account type");
}

public async getAccountsForWallet(
walletAddress: string,
): Promise<WalletAccount[]> {
const pubKeys = await this.getMetadataAddressesForWallet(walletAddress);
const mints = await this.getOwnedTokenAccountsForWallet(walletAddress);
const metadatas = await this.metaplex
.nfts()
.findAllByMintList({ mints: pubKeys })
.findAllByMintList({ mints })
.run();

const candyMachines = await this.metaplex
.candyMachines()
.findAllBy({
type: "wallet",
type: "authority",
publicKey: new PublicKey(walletAddress),
})
.run();
Expand Down Expand Up @@ -106,10 +104,10 @@ export class Registry {
);
}

public async getMetadataAddressesForWallet(walletAddress: string) {
return await TokenMetadataProgram.metadataV1Accounts(this.metaplex)
private async getOwnedTokenAccountsForWallet(walletAddress: string) {
return await TokenProgram.tokenAccounts(this.metaplex)
.selectMint()
.whereCreator(1, new PublicKey(walletAddress))
.whereOwner(new PublicKey(walletAddress))
.getDataAsPublicKeys();
}
}
13 changes: 7 additions & 6 deletions packages/solana/src/contracts/nft-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export class NFTCollection {
private metaplex: Metaplex;
private storage: IStorage;
private nft: NFTHelper;
collectionMintAddress: PublicKey;
public publicKey: PublicKey;
public accountType = "nft-collection" as const;

constructor(
collectionMintAddress: string,
Expand All @@ -32,13 +33,13 @@ export class NFTCollection {
this.storage = storage;
this.metaplex = metaplex;
this.nft = new NFTHelper(metaplex);
this.collectionMintAddress = new PublicKey(collectionMintAddress);
this.publicKey = new PublicKey(collectionMintAddress);
}

async getMetadata(): Promise<NFTCollectionMetadata> {
const metadata = await this.metaplex
.nfts()
.findByMint({ mintAddress: this.collectionMintAddress })
.findByMint({ mintAddress: this.publicKey })
.run();

return this.nft.toNFTMetadata(metadata);
Expand All @@ -52,7 +53,7 @@ export class NFTCollection {
const allSignatures: ConfirmedSignatureInfo[] = [];
// This returns the first 1000, so we need to loop through until we run out of signatures to get.
let signatures = await this.metaplex.connection.getSignaturesForAddress(
this.collectionMintAddress,
this.publicKey,
);

allSignatures.push(...signatures);
Expand All @@ -61,7 +62,7 @@ export class NFTCollection {
before: signatures[signatures.length - 1]?.signature,
};
signatures = await this.metaplex.connection.getSignaturesForAddress(
this.collectionMintAddress,
this.publicKey,
options,
);
allSignatures.push(...signatures);
Expand Down Expand Up @@ -213,7 +214,7 @@ export class NFTCollection {
name: metadata.name || "",
uri,
sellerFeeBasisPoints: 0,
collection: this.collectionMintAddress,
collection: this.publicKey,
collectionAuthority: this.metaplex.identity(),
tokenOwner: new PublicKey(to),
// Always sets max supply to unlimited so editions can be minted
Expand Down
9 changes: 5 additions & 4 deletions packages/solana/src/contracts/nft-drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ export class NFTDrop {
private metaplex: Metaplex;
private storage: IStorage;
private nft: NFTHelper;
public dropMintAddress: PublicKey;
public accountType = "nft-drop" as const;
public publicKey: PublicKey;
public claimConditions: ClaimConditions;

constructor(dropMintAddress: string, metaplex: Metaplex, storage: IStorage) {
this.storage = storage;
this.metaplex = metaplex;
this.nft = new NFTHelper(metaplex);
this.dropMintAddress = new PublicKey(dropMintAddress);
this.publicKey = new PublicKey(dropMintAddress);
this.claimConditions = new ClaimConditions(dropMintAddress, metaplex);
}

Expand Down Expand Up @@ -57,7 +58,7 @@ export class NFTDrop {
async getAllClaimed(): Promise<NFTMetadata[]> {
const nfts = await this.metaplex
.candyMachines()
.findMintedNfts({ candyMachine: this.dropMintAddress })
.findMintedNfts({ candyMachine: this.publicKey })
.run();

const metadatas = nfts.map((nft) => this.nft.toNFTMetadata(nft));
Expand Down Expand Up @@ -128,7 +129,7 @@ export class NFTDrop {
private async getCandyMachine() {
return this.metaplex
.candyMachines()
.findByAddress({ address: this.dropMintAddress })
.findByAddress({ address: this.publicKey })
.run();
}
}
15 changes: 8 additions & 7 deletions packages/solana/src/contracts/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,26 @@ export class Token {
private connection: Connection;
private metaplex: Metaplex;
private storage: IStorage;
tokenMintAddress: PublicKey;
public accountType = "token" as const;
public publicKey: PublicKey;

constructor(tokenMintAddress: string, metaplex: Metaplex, storage: IStorage) {
this.storage = storage;
this.metaplex = metaplex;
this.connection = metaplex.connection;
this.tokenMintAddress = new PublicKey(tokenMintAddress);
this.publicKey = new PublicKey(tokenMintAddress);
}

async getMint() {
return await this.metaplex
.tokens()
.findMintByAddress({ address: this.tokenMintAddress })
.findMintByAddress({ address: this.publicKey })
.run(); // TODO abstract types away
}

async getMetadata(): Promise<TokenMetadata> {
const mint = await this.getMint();
const addr = findMetadataPda(this.tokenMintAddress);
const addr = findMetadataPda(this.publicKey);
const account = await this.metaplex.rpc().getAccount(addr);
const meta = toMetadata(toMetadataAccount(account));
return {
Expand All @@ -66,7 +67,7 @@ export class Token {
.tokens()
.mint({
amount: token(amountParsed, info.decimals),
mintAddress: this.tokenMintAddress,
mintAddress: this.publicKey,
})
.run();
return {
Expand All @@ -82,7 +83,7 @@ export class Token {
const result = await this.metaplex
.tokens()
.send({
mintAddress: this.tokenMintAddress,
mintAddress: this.publicKey,
amount: token(amount, info.decimals),
toOwner: new PublicKey(receiverAddress),
})
Expand All @@ -99,7 +100,7 @@ export class Token {
async balanceOf(walletAddress: string): Promise<CurrencyValue> {
const mint = await this.getMint();
const addr = await getAssociatedTokenAddress(
this.tokenMintAddress,
this.publicKey,
new PublicKey(walletAddress),
);
try {
Expand Down
2 changes: 1 addition & 1 deletion packages/solana/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export {
TokenMetadataInputSchema,
NFTCollectionMetadataInputSchema,
} from "./types/contracts";
export { NFTDropContractSchema } from "./types/contracts/nft-drop";
export { NFTDropContractInputSchema } from "./types/contracts/nft-drop";
Loading