Merge branch 'master' of github.com:metaplex-foundation/metaplex into f
This commit is contained in:
commit
db1a6980ee
|
@ -16,7 +16,7 @@ Metaplex is comprised of two core components: an on-chain program, and a self-ho
|
||||||
|
|
||||||
If you want to deep dive on the Architecture, you can do so here:
|
If you want to deep dive on the Architecture, you can do so here:
|
||||||
|
|
||||||
https://www.notion.so/Metaplex-Developer-Guide-afefbc19841744c28587ab948a08cfac
|
https://docs.metaplex.com/
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ yarn deploy
|
||||||
|
|
||||||
## Vercel
|
## Vercel
|
||||||
|
|
||||||
To publish the Metaplex app to Vercel, you first need to visit [https://vercel.com/](https://vercel.com/) and create a new project linked to your github repo.
|
To publish the Metaplex app to Vercel, you first need to visit [https://vercel.com/](https://vercel.com/) and create a new project linked to your github repo. Then, create a `pages/` directory under `js`.
|
||||||
|
|
||||||
After that, configure this project with the following settings:
|
After that, configure this project with the following settings:
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@project-serum/anchor": "^0.13.2",
|
"@project-serum/anchor": "^0.13.2",
|
||||||
"@solana/web3.js": "^1.24.1",
|
|
||||||
"arweave": "^1.10.16",
|
"arweave": "^1.10.16",
|
||||||
"bn.js": "^5.2.0",
|
"bn.js": "^5.2.0",
|
||||||
"commander": "^8.1.0",
|
"commander": "^8.1.0",
|
||||||
|
|
|
@ -389,11 +389,12 @@ program
|
||||||
[],
|
[],
|
||||||
'single',
|
'single',
|
||||||
);
|
);
|
||||||
|
console.info('transaction for arweave payment:', tx);
|
||||||
|
|
||||||
// data.append('tags', JSON.stringify(tags));
|
// data.append('tags', JSON.stringify(tags));
|
||||||
// payment transaction
|
// payment transaction
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('transaction', tx);
|
data.append('transaction', tx['txid']);
|
||||||
data.append('env', ENV);
|
data.append('env', ENV);
|
||||||
data.append('file[]', fs.createReadStream(image), `image.png`);
|
data.append('file[]', fs.createReadStream(image), `image.png`);
|
||||||
data.append('file[]', manifestBuffer, 'metadata.json');
|
data.append('file[]', manifestBuffer, 'metadata.json');
|
||||||
|
@ -432,9 +433,10 @@ program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(cacheContent.items);
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
chunks(Array.from(Array(images.length).keys()), 1000).map(
|
chunks(Array.from(Array(keys.length).keys()), 1000).map(
|
||||||
async allIndexesInSlice => {
|
async allIndexesInSlice => {
|
||||||
for (
|
for (
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
@ -443,30 +445,23 @@ program
|
||||||
) {
|
) {
|
||||||
const indexes = allIndexesInSlice.slice(offset, offset + 10);
|
const indexes = allIndexesInSlice.slice(offset, offset + 10);
|
||||||
const onChain = indexes.filter(i => {
|
const onChain = indexes.filter(i => {
|
||||||
const index = images[i].replace(extension, '').split('/').pop();
|
const index = keys[i];
|
||||||
return cacheContent.items[index].onChain;
|
return cacheContent.items[index]?.onChain;
|
||||||
});
|
});
|
||||||
const ind = images[indexes[0]]
|
const ind = keys[indexes[0]];
|
||||||
.replace(extension, '')
|
|
||||||
.split('/')
|
|
||||||
.pop();
|
|
||||||
|
|
||||||
if (onChain.length != indexes.length) {
|
if (onChain.length != indexes.length) {
|
||||||
console.log(
|
console.log(
|
||||||
'Writing indices ',
|
'Writing indices ',
|
||||||
ind,
|
ind,
|
||||||
'-',
|
'-',
|
||||||
parseInt(ind) + indexes.length,
|
keys[indexes[indexes.length - 1]],
|
||||||
);
|
);
|
||||||
const txId = await anchorProgram.rpc.addConfigLines(
|
const txId = await anchorProgram.rpc.addConfigLines(
|
||||||
ind,
|
ind,
|
||||||
indexes.map(i => ({
|
indexes.map(i => ({
|
||||||
uri: cacheContent.items[
|
uri: cacheContent.items[keys[i]].link,
|
||||||
images[i].replace(extension, '').split('/').pop()
|
name: cacheContent.items[keys[i]].name,
|
||||||
].link,
|
|
||||||
name: cacheContent.items[
|
|
||||||
images[i].replace(extension, '').split('/').pop()
|
|
||||||
].name,
|
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
accounts: {
|
accounts: {
|
||||||
|
@ -477,12 +472,8 @@ program
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
indexes.forEach(i => {
|
indexes.forEach(i => {
|
||||||
cacheContent.items[
|
cacheContent.items[keys[i]] = {
|
||||||
images[i].replace(extension, '').split('/').pop()
|
...cacheContent.items[keys[i]],
|
||||||
] = {
|
|
||||||
...cacheContent.items[
|
|
||||||
images[i].replace(extension, '').split('/').pop()
|
|
||||||
],
|
|
||||||
onChain: true,
|
onChain: true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -610,12 +601,12 @@ program
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Done');
|
console.log(`Done: CANDYMACHINE: ${candyMachine.toBase58()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('mint_token_as_candy_machine_owner')
|
.command('mint_one_token')
|
||||||
.option('-k, --keypair <path>', 'Solana wallet')
|
.option('-k, --keypair <path>', `The purchaser's wallet key`)
|
||||||
.option('-c, --cache-name <path>', 'Cache file name')
|
.option('-c, --cache-name <path>', 'Cache file name')
|
||||||
.action(async (directory, cmd) => {
|
.action(async (directory, cmd) => {
|
||||||
const solConnection = new anchor.web3.Connection(
|
const solConnection = new anchor.web3.Connection(
|
||||||
|
@ -648,6 +639,7 @@ program
|
||||||
config,
|
config,
|
||||||
cachedContent.program.uuid,
|
cachedContent.program.uuid,
|
||||||
);
|
);
|
||||||
|
const candy = await anchorProgram.account.candyMachine.fetch(candyMachine);
|
||||||
const metadata = await getMetadata(mint.publicKey);
|
const metadata = await getMetadata(mint.publicKey);
|
||||||
const masterEdition = await getMasterEdition(mint.publicKey);
|
const masterEdition = await getMasterEdition(mint.publicKey);
|
||||||
const tx = await anchorProgram.rpc.mintNft({
|
const tx = await anchorProgram.rpc.mintNft({
|
||||||
|
@ -655,7 +647,8 @@ program
|
||||||
config: config,
|
config: config,
|
||||||
candyMachine: candyMachine,
|
candyMachine: candyMachine,
|
||||||
payer: walletKey.publicKey,
|
payer: walletKey.publicKey,
|
||||||
wallet: walletKey.publicKey,
|
//@ts-ignore
|
||||||
|
wallet: candy.wallet,
|
||||||
mint: mint.publicKey,
|
mint: mint.publicKey,
|
||||||
metadata,
|
metadata,
|
||||||
masterEdition,
|
masterEdition,
|
||||||
|
@ -720,6 +713,8 @@ program
|
||||||
const config = await solConnection.getAccountInfo(
|
const config = await solConnection.getAccountInfo(
|
||||||
new PublicKey(cachedContent.program.config),
|
new PublicKey(cachedContent.program.config),
|
||||||
);
|
);
|
||||||
|
const number = new BN(config.data.slice(247, 247 + 4), undefined, 'le');
|
||||||
|
console.log('Number', number.toNumber());
|
||||||
|
|
||||||
const keys = Object.keys(cachedContent.items);
|
const keys = Object.keys(cachedContent.items);
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
REACT_APP_STORE_OWNER_ADDRESS_ADDRESS=CduMjFZLBeg3A9wMP3hQCoU1RQzzCpgSvQNXfCi1GCSB
|
REACT_APP_STORE_OWNER_ADDRESS_ADDRESS=
|
||||||
REACT_APP_STORE_ADDRESS=
|
REACT_APP_STORE_ADDRESS=
|
||||||
REACT_APP_BIG_STORE=FALSE
|
REACT_APP_BIG_STORE=FALSE
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Keypair, TransactionInstruction } from '@solana/web3.js';
|
||||||
|
import { Token } from '@solana/spl-token';
|
||||||
|
import {
|
||||||
|
createAssociatedTokenAccountInstruction,
|
||||||
|
createMint,
|
||||||
|
findProgramAddress,
|
||||||
|
programIds,
|
||||||
|
StringPublicKey,
|
||||||
|
toPublicKey,
|
||||||
|
} from '@oyster/common';
|
||||||
|
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||||
|
|
||||||
|
export async function createMintAndAccountWithOne(
|
||||||
|
wallet: any,
|
||||||
|
receiverWallet: StringPublicKey,
|
||||||
|
mintRent: any,
|
||||||
|
instructions: TransactionInstruction[],
|
||||||
|
signers: Keypair[],
|
||||||
|
): Promise<{ mint: StringPublicKey; account: StringPublicKey }> {
|
||||||
|
if (!wallet.publicKey) throw new WalletNotConnectedError();
|
||||||
|
|
||||||
|
const mint = createMint(
|
||||||
|
instructions,
|
||||||
|
wallet.publicKey,
|
||||||
|
mintRent,
|
||||||
|
0,
|
||||||
|
wallet.publicKey,
|
||||||
|
wallet.publicKey,
|
||||||
|
signers,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PROGRAM_IDS = programIds();
|
||||||
|
|
||||||
|
const account: StringPublicKey = (
|
||||||
|
await findProgramAddress(
|
||||||
|
[
|
||||||
|
toPublicKey(receiverWallet).toBuffer(),
|
||||||
|
PROGRAM_IDS.token.toBuffer(),
|
||||||
|
mint.toBuffer(),
|
||||||
|
],
|
||||||
|
PROGRAM_IDS.associatedToken,
|
||||||
|
)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
createAssociatedTokenAccountInstruction(
|
||||||
|
instructions,
|
||||||
|
toPublicKey(account),
|
||||||
|
wallet.publicKey,
|
||||||
|
toPublicKey(receiverWallet),
|
||||||
|
mint,
|
||||||
|
);
|
||||||
|
|
||||||
|
instructions.push(
|
||||||
|
Token.createMintToInstruction(
|
||||||
|
PROGRAM_IDS.token,
|
||||||
|
mint,
|
||||||
|
toPublicKey(account),
|
||||||
|
wallet.publicKey,
|
||||||
|
[],
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { mint: mint.toBase58(), account };
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { Connection, Keypair, TransactionInstruction } from '@solana/web3.js';
|
||||||
|
import {
|
||||||
|
sendTransactions,
|
||||||
|
sendTransactionWithRetry,
|
||||||
|
SequenceType,
|
||||||
|
StringPublicKey,
|
||||||
|
TokenAccount,
|
||||||
|
} from '@oyster/common';
|
||||||
|
import { setupMintEditionIntoWalletInstructions } from './setupMintEditionIntoWalletInstructions';
|
||||||
|
import { Art } from '../types';
|
||||||
|
import { WalletContextState } from '@solana/wallet-adapter-react';
|
||||||
|
|
||||||
|
// TODO: Refactor. Extract batching logic,
|
||||||
|
// as the similar one is used in settle.ts and convertMasterEditions.ts
|
||||||
|
const MINT_TRANSACTION_SIZE = 5;
|
||||||
|
const BATCH_SIZE = 10;
|
||||||
|
|
||||||
|
export async function mintEditionsToWallet(
|
||||||
|
art: Art,
|
||||||
|
wallet: WalletContextState,
|
||||||
|
connection: Connection,
|
||||||
|
mintTokenAccount: TokenAccount,
|
||||||
|
editions: number = 1,
|
||||||
|
mintDestination: StringPublicKey,
|
||||||
|
) {
|
||||||
|
let signers: Array<Array<Keypair[]>> = [];
|
||||||
|
let instructions: Array<Array<TransactionInstruction[]>> = [];
|
||||||
|
|
||||||
|
let currSignerBatch: Array<Keypair[]> = [];
|
||||||
|
let currInstrBatch: Array<TransactionInstruction[]> = [];
|
||||||
|
|
||||||
|
let mintEditionIntoWalletSigners: Keypair[] = [];
|
||||||
|
let mintEditionIntoWalletInstructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
|
// TODO replace all this with payer account so user doesnt need to click approve several times.
|
||||||
|
|
||||||
|
// Overall we have 10 parallel txns.
|
||||||
|
// That's what this loop is building.
|
||||||
|
for (let i = 0; i < editions; i++) {
|
||||||
|
console.log('Minting', i);
|
||||||
|
await setupMintEditionIntoWalletInstructions(
|
||||||
|
art,
|
||||||
|
wallet,
|
||||||
|
connection,
|
||||||
|
mintTokenAccount,
|
||||||
|
new BN(art.supply! + 1 + i),
|
||||||
|
mintEditionIntoWalletInstructions,
|
||||||
|
mintEditionIntoWalletSigners,
|
||||||
|
mintDestination,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mintEditionIntoWalletInstructions.length === MINT_TRANSACTION_SIZE) {
|
||||||
|
currSignerBatch.push(mintEditionIntoWalletSigners);
|
||||||
|
currInstrBatch.push(mintEditionIntoWalletInstructions);
|
||||||
|
mintEditionIntoWalletSigners = [];
|
||||||
|
mintEditionIntoWalletInstructions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currInstrBatch.length === BATCH_SIZE) {
|
||||||
|
signers.push(currSignerBatch);
|
||||||
|
instructions.push(currInstrBatch);
|
||||||
|
currSignerBatch = [];
|
||||||
|
currInstrBatch = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mintEditionIntoWalletInstructions.length < MINT_TRANSACTION_SIZE &&
|
||||||
|
mintEditionIntoWalletInstructions.length > 0
|
||||||
|
) {
|
||||||
|
currSignerBatch.push(mintEditionIntoWalletSigners);
|
||||||
|
currInstrBatch.push(mintEditionIntoWalletInstructions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currInstrBatch.length <= BATCH_SIZE && currInstrBatch.length > 0) {
|
||||||
|
// add the last one on
|
||||||
|
signers.push(currSignerBatch);
|
||||||
|
instructions.push(currInstrBatch);
|
||||||
|
}
|
||||||
|
console.log('Instructions', instructions);
|
||||||
|
for (let i = 0; i < instructions.length; i++) {
|
||||||
|
const instructionBatch = instructions[i];
|
||||||
|
const signerBatch = signers[i];
|
||||||
|
console.log('Running batch', i);
|
||||||
|
if (instructionBatch.length >= 2)
|
||||||
|
// Pump em through!
|
||||||
|
await sendTransactions(
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
instructionBatch,
|
||||||
|
signerBatch,
|
||||||
|
SequenceType.StopOnFailure,
|
||||||
|
'single',
|
||||||
|
);
|
||||||
|
else
|
||||||
|
await sendTransactionWithRetry(
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
instructionBatch[0],
|
||||||
|
signerBatch[0],
|
||||||
|
'single',
|
||||||
|
);
|
||||||
|
console.log('Done');
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,15 +16,12 @@ import {
|
||||||
sendTransactionsWithManualRetry,
|
sendTransactionsWithManualRetry,
|
||||||
MasterEditionV1,
|
MasterEditionV1,
|
||||||
MasterEditionV2,
|
MasterEditionV2,
|
||||||
findProgramAddress,
|
|
||||||
createAssociatedTokenAccountInstruction,
|
|
||||||
deprecatedMintNewEditionFromMasterEditionViaPrintingToken,
|
deprecatedMintNewEditionFromMasterEditionViaPrintingToken,
|
||||||
MetadataKey,
|
MetadataKey,
|
||||||
TokenAccountParser,
|
TokenAccountParser,
|
||||||
BidderMetadata,
|
BidderMetadata,
|
||||||
getEditionMarkPda,
|
getEditionMarkPda,
|
||||||
decodeEditionMarker,
|
decodeEditionMarker,
|
||||||
BidStateType,
|
|
||||||
StringPublicKey,
|
StringPublicKey,
|
||||||
toPublicKey,
|
toPublicKey,
|
||||||
WalletSigner,
|
WalletSigner,
|
||||||
|
@ -44,13 +41,13 @@ import {
|
||||||
PrizeTrackingTicket,
|
PrizeTrackingTicket,
|
||||||
getPrizeTrackingTicket,
|
getPrizeTrackingTicket,
|
||||||
BidRedemptionTicket,
|
BidRedemptionTicket,
|
||||||
getBidRedemption,
|
|
||||||
} from '../models/metaplex';
|
} from '../models/metaplex';
|
||||||
import { claimBid } from '../models/metaplex/claimBid';
|
import { claimBid } from '../models/metaplex/claimBid';
|
||||||
import { setupCancelBid } from './cancelBid';
|
import { setupCancelBid } from './cancelBid';
|
||||||
import { deprecatedPopulateParticipationPrintingAccount } from '../models/metaplex/deprecatedPopulateParticipationPrintingAccount';
|
import { deprecatedPopulateParticipationPrintingAccount } from '../models/metaplex/deprecatedPopulateParticipationPrintingAccount';
|
||||||
import { setupPlaceBid } from './sendPlaceBid';
|
import { setupPlaceBid } from './sendPlaceBid';
|
||||||
import { claimUnusedPrizes } from './claimUnusedPrizes';
|
import { claimUnusedPrizes } from './claimUnusedPrizes';
|
||||||
|
import { createMintAndAccountWithOne } from './createMintAndAccountWithOne';
|
||||||
import { BN } from 'bn.js';
|
import { BN } from 'bn.js';
|
||||||
import { QUOTE_MINT } from '../constants';
|
import { QUOTE_MINT } from '../constants';
|
||||||
import {
|
import {
|
||||||
|
@ -414,60 +411,6 @@ async function setupRedeemFullRightsTransferInstructions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createMintAndAccountWithOne(
|
|
||||||
wallet: WalletSigner,
|
|
||||||
receiverWallet: StringPublicKey,
|
|
||||||
mintRent: any,
|
|
||||||
instructions: TransactionInstruction[],
|
|
||||||
signers: Keypair[],
|
|
||||||
): Promise<{ mint: StringPublicKey; account: StringPublicKey }> {
|
|
||||||
if (!wallet.publicKey) throw new WalletNotConnectedError();
|
|
||||||
|
|
||||||
const mint = createMint(
|
|
||||||
instructions,
|
|
||||||
wallet.publicKey,
|
|
||||||
mintRent,
|
|
||||||
0,
|
|
||||||
wallet.publicKey,
|
|
||||||
wallet.publicKey,
|
|
||||||
signers,
|
|
||||||
);
|
|
||||||
|
|
||||||
const PROGRAM_IDS = programIds();
|
|
||||||
|
|
||||||
const account: StringPublicKey = (
|
|
||||||
await findProgramAddress(
|
|
||||||
[
|
|
||||||
toPublicKey(receiverWallet).toBuffer(),
|
|
||||||
PROGRAM_IDS.token.toBuffer(),
|
|
||||||
mint.toBuffer(),
|
|
||||||
],
|
|
||||||
PROGRAM_IDS.associatedToken,
|
|
||||||
)
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
createAssociatedTokenAccountInstruction(
|
|
||||||
instructions,
|
|
||||||
toPublicKey(account),
|
|
||||||
wallet.publicKey,
|
|
||||||
toPublicKey(receiverWallet),
|
|
||||||
mint,
|
|
||||||
);
|
|
||||||
|
|
||||||
instructions.push(
|
|
||||||
Token.createMintToInstruction(
|
|
||||||
PROGRAM_IDS.token,
|
|
||||||
mint,
|
|
||||||
toPublicKey(account),
|
|
||||||
wallet.publicKey,
|
|
||||||
[],
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return { mint: mint.toBase58(), account };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setupRedeemPrintingV2Instructions(
|
export async function setupRedeemPrintingV2Instructions(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
auctionView: AuctionView,
|
auctionView: AuctionView,
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { Connection } from '@solana/web3.js';
|
||||||
|
import { MintLayout } from '@solana/spl-token';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import {
|
||||||
|
mintNewEditionFromMasterEditionViaToken,
|
||||||
|
StringPublicKey,
|
||||||
|
TokenAccount,
|
||||||
|
} from '@oyster/common';
|
||||||
|
import { createMintAndAccountWithOne } from './createMintAndAccountWithOne';
|
||||||
|
import { Art } from '../types';
|
||||||
|
import { WalletContextState } from '@solana/wallet-adapter-react';
|
||||||
|
|
||||||
|
export async function setupMintEditionIntoWalletInstructions(
|
||||||
|
art: Art,
|
||||||
|
wallet: WalletContextState,
|
||||||
|
connection: Connection,
|
||||||
|
mintTokenAccount: TokenAccount,
|
||||||
|
edition: BN,
|
||||||
|
instructions: any,
|
||||||
|
signers: any,
|
||||||
|
mintDestination: StringPublicKey,
|
||||||
|
) {
|
||||||
|
if (!art.mint) throw new Error('Art mint is not provided');
|
||||||
|
if (typeof art.supply === 'undefined') {
|
||||||
|
throw new Error('Art supply is not provided');
|
||||||
|
}
|
||||||
|
if (!wallet.publicKey) throw new Error('Wallet pubKey is not provided');
|
||||||
|
if (!mintTokenAccount) {
|
||||||
|
throw new Error('Art mint token account is not provided');
|
||||||
|
}
|
||||||
|
const walletPubKey = wallet.publicKey.toString();
|
||||||
|
const { mint: tokenMint } = art;
|
||||||
|
const { pubkey: mintTokenAccountPubKey } = mintTokenAccount;
|
||||||
|
const mintTokenAccountOwner = mintTokenAccount.info.owner.toString();
|
||||||
|
|
||||||
|
const mintRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
MintLayout.span,
|
||||||
|
);
|
||||||
|
const { mint: newMint } = await createMintAndAccountWithOne(
|
||||||
|
wallet,
|
||||||
|
mintDestination,
|
||||||
|
mintRentExempt,
|
||||||
|
instructions,
|
||||||
|
signers,
|
||||||
|
);
|
||||||
|
|
||||||
|
await mintNewEditionFromMasterEditionViaToken(
|
||||||
|
newMint,
|
||||||
|
tokenMint,
|
||||||
|
walletPubKey,
|
||||||
|
walletPubKey,
|
||||||
|
mintTokenAccountOwner,
|
||||||
|
mintTokenAccountPubKey,
|
||||||
|
instructions,
|
||||||
|
walletPubKey,
|
||||||
|
edition,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,248 @@
|
||||||
|
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
||||||
|
import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
|
||||||
|
import { MintLayout, AccountLayout } from '@solana/spl-token';
|
||||||
|
import { Button, Form, Input, Modal, InputNumber } from 'antd';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import {
|
||||||
|
decodeMasterEdition,
|
||||||
|
MAX_EDITION_LEN,
|
||||||
|
MAX_METADATA_LEN,
|
||||||
|
MetadataKey,
|
||||||
|
MetaplexOverlay,
|
||||||
|
useConnection,
|
||||||
|
useUserAccounts,
|
||||||
|
} from '@oyster/common';
|
||||||
|
import { useArt } from '../../hooks';
|
||||||
|
import { mintEditionsToWallet } from '../../actions/mintEditionsIntoWallet';
|
||||||
|
import { ArtType } from '../../types';
|
||||||
|
import { Confetti } from '../Confetti';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useWallet } from '@solana/wallet-adapter-react';
|
||||||
|
|
||||||
|
interface ArtMintingProps {
|
||||||
|
id: string;
|
||||||
|
onMint: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArtMinting = ({ id, onMint }: ArtMintingProps) => {
|
||||||
|
const wallet = useWallet();
|
||||||
|
const connection = useConnection();
|
||||||
|
const { accountByMint } = useUserAccounts();
|
||||||
|
const [showMintModal, setShowMintModal] = useState<boolean>(false);
|
||||||
|
const [showCongrats, setShowCongrats] = useState<boolean>(false);
|
||||||
|
const [mintingDestination, setMintingDestination] = useState<string>('');
|
||||||
|
const [editions, setEditions] = useState<number>(1);
|
||||||
|
const [totalCost, setTotalCost] = useState<number>(0);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const art = useArt(id);
|
||||||
|
|
||||||
|
const walletPubKey = wallet?.publicKey?.toString() || '';
|
||||||
|
const maxEditionsToMint = art.maxSupply! - art.supply!;
|
||||||
|
const isArtMasterEdition = art.type === ArtType.Master;
|
||||||
|
const artMintTokenAccount = accountByMint.get(art.mint!);
|
||||||
|
const isArtOwnedByUser =
|
||||||
|
((accountByMint.has(art.mint!) &&
|
||||||
|
artMintTokenAccount?.info.amount.toNumber()) ||
|
||||||
|
0) > 0;
|
||||||
|
const isMasterEditionV1 = artMintTokenAccount
|
||||||
|
? decodeMasterEdition(artMintTokenAccount.account.data).key ===
|
||||||
|
MetadataKey.MasterEditionV1
|
||||||
|
: false;
|
||||||
|
const renderMintEdition =
|
||||||
|
isArtMasterEdition &&
|
||||||
|
isArtOwnedByUser &&
|
||||||
|
!isMasterEditionV1 &&
|
||||||
|
maxEditionsToMint !== 0;
|
||||||
|
|
||||||
|
const mintingDestinationErr = useMemo(() => {
|
||||||
|
if (!mintingDestination) return 'Required';
|
||||||
|
|
||||||
|
try {
|
||||||
|
new PublicKey(mintingDestination);
|
||||||
|
return '';
|
||||||
|
} catch (e) {
|
||||||
|
return 'Invalid address format';
|
||||||
|
}
|
||||||
|
}, [mintingDestination]);
|
||||||
|
|
||||||
|
const isMintingDisabled =
|
||||||
|
isLoading || editions < 1 || Boolean(mintingDestinationErr);
|
||||||
|
|
||||||
|
const debouncedEditionsChangeHandler = useCallback(
|
||||||
|
debounce(val => {
|
||||||
|
setEditions(val < 1 ? 1 : val);
|
||||||
|
}, 300),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editions < 1) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const mintRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
MintLayout.span,
|
||||||
|
);
|
||||||
|
const accountRentExempt =
|
||||||
|
await connection.getMinimumBalanceForRentExemption(AccountLayout.span);
|
||||||
|
const metadataRentExempt =
|
||||||
|
await connection.getMinimumBalanceForRentExemption(MAX_METADATA_LEN);
|
||||||
|
const editionRentExempt =
|
||||||
|
await connection.getMinimumBalanceForRentExemption(MAX_EDITION_LEN);
|
||||||
|
|
||||||
|
const cost =
|
||||||
|
((mintRentExempt +
|
||||||
|
accountRentExempt +
|
||||||
|
metadataRentExempt +
|
||||||
|
editionRentExempt) *
|
||||||
|
editions) /
|
||||||
|
LAMPORTS_PER_SOL;
|
||||||
|
|
||||||
|
setTotalCost(cost);
|
||||||
|
})();
|
||||||
|
}, [connection, editions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!walletPubKey) return;
|
||||||
|
|
||||||
|
setMintingDestination(walletPubKey);
|
||||||
|
}, [walletPubKey]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return debouncedEditionsChangeHandler.cancel();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSuccessfulMint = () => {
|
||||||
|
setShowMintModal(false);
|
||||||
|
setMintingDestination(walletPubKey);
|
||||||
|
setEditions(1);
|
||||||
|
setShowCongrats(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mint = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
await mintEditionsToWallet(
|
||||||
|
art,
|
||||||
|
wallet!,
|
||||||
|
connection,
|
||||||
|
artMintTokenAccount!,
|
||||||
|
editions,
|
||||||
|
mintingDestination,
|
||||||
|
);
|
||||||
|
onSuccessfulMint();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderMintEdition && (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
className="action-btn"
|
||||||
|
style={{ marginTop: 20 }}
|
||||||
|
onClick={() => setShowMintModal(true)}
|
||||||
|
>
|
||||||
|
Mint
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
visible={showMintModal}
|
||||||
|
centered
|
||||||
|
okText="Mint"
|
||||||
|
closable={!isLoading}
|
||||||
|
okButtonProps={{
|
||||||
|
disabled: isMintingDisabled,
|
||||||
|
}}
|
||||||
|
cancelButtonProps={{ disabled: isLoading }}
|
||||||
|
onOk={mint}
|
||||||
|
onCancel={() => setShowMintModal(false)}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column',
|
||||||
|
paddingTop: 30,
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
label={<h3>Mint to</h3>}
|
||||||
|
labelAlign="left"
|
||||||
|
colon={false}
|
||||||
|
validateStatus={mintingDestinationErr ? 'error' : 'success'}
|
||||||
|
help={mintingDestinationErr}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Address to mint edition to"
|
||||||
|
value={mintingDestination}
|
||||||
|
onChange={e => {
|
||||||
|
setMintingDestination(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column',
|
||||||
|
paddingTop: 30,
|
||||||
|
}}
|
||||||
|
label={<h3>Number of editions to mint</h3>}
|
||||||
|
labelAlign="left"
|
||||||
|
colon={false}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
type="number"
|
||||||
|
placeholder="1"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
min={1}
|
||||||
|
max={maxEditionsToMint}
|
||||||
|
value={editions}
|
||||||
|
precision={0}
|
||||||
|
onChange={debouncedEditionsChangeHandler}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<div>Total cost: {`◎${totalCost}`}</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<MetaplexOverlay visible={showCongrats}>
|
||||||
|
<Confetti />
|
||||||
|
<h1
|
||||||
|
className="title"
|
||||||
|
style={{
|
||||||
|
fontSize: '3rem',
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Congratulations
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: '2rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New editions have been minted please view your NFTs in{' '}
|
||||||
|
<Link to="/artworks">My Items</Link>.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await onMint();
|
||||||
|
setShowCongrats(false);
|
||||||
|
}}
|
||||||
|
className="overlay-btn"
|
||||||
|
>
|
||||||
|
Got it
|
||||||
|
</Button>
|
||||||
|
</MetaplexOverlay>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,21 +1,33 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Row, Col, Divider, Layout, Tag, Button, Skeleton, List, Card } from 'antd';
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Divider,
|
||||||
|
Layout,
|
||||||
|
Tag,
|
||||||
|
Button,
|
||||||
|
Skeleton,
|
||||||
|
List,
|
||||||
|
Card,
|
||||||
|
} from 'antd';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useArt, useExtendedArt } from './../../hooks';
|
import { useArt, useExtendedArt } from '../../hooks';
|
||||||
|
|
||||||
import { ArtContent } from '../../components/ArtContent';
|
import { ArtContent } from '../../components/ArtContent';
|
||||||
import { shortenAddress, useConnection } from '@oyster/common';
|
import { shortenAddress, useConnection } from '@oyster/common';
|
||||||
import { useWallet } from '@solana/wallet-adapter-react';
|
import { useWallet } from '@solana/wallet-adapter-react';
|
||||||
import { MetaAvatar } from '../../components/MetaAvatar';
|
import { MetaAvatar } from '../../components/MetaAvatar';
|
||||||
import { sendSignMetadata } from '../../actions/sendSignMetadata';
|
import { sendSignMetadata } from '../../actions/sendSignMetadata';
|
||||||
import { ViewOn } from './../../components/ViewOn';
|
import { ViewOn } from '../../components/ViewOn';
|
||||||
import { ArtType } from '../../types';
|
import { ArtType } from '../../types';
|
||||||
|
import { ArtMinting } from '../../components/ArtMinting';
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
||||||
export const ArtView = () => {
|
export const ArtView = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const wallet = useWallet();
|
const wallet = useWallet();
|
||||||
|
const [remountArtMinting, setRemountArtMinting] = useState(0);
|
||||||
|
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const art = useArt(id);
|
const art = useArt(id);
|
||||||
|
@ -39,7 +51,7 @@ export const ArtView = () => {
|
||||||
const description = data?.description;
|
const description = data?.description;
|
||||||
const attributes = data?.attributes;
|
const attributes = data?.attributes;
|
||||||
|
|
||||||
const pubkey = wallet.publicKey?.toBase58() || '';
|
const pubkey = wallet?.publicKey?.toBase58() || '';
|
||||||
|
|
||||||
const tag = (
|
const tag = (
|
||||||
<div className="info-header">
|
<div className="info-header">
|
||||||
|
@ -181,6 +193,13 @@ export const ArtView = () => {
|
||||||
>
|
>
|
||||||
Mark as Sold
|
Mark as Sold
|
||||||
</Button> */}
|
</Button> */}
|
||||||
|
|
||||||
|
{/* TODO: Add conversion of MasterEditionV1 to MasterEditionV2 */}
|
||||||
|
<ArtMinting
|
||||||
|
id={id}
|
||||||
|
key={remountArtMinting}
|
||||||
|
onMint={async () => await setRemountArtMinting(prev => prev + 1)}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="12">
|
<Col span="12">
|
||||||
<Divider />
|
<Divider />
|
||||||
|
@ -197,23 +216,22 @@ export const ArtView = () => {
|
||||||
<div className="info-content">{art.about}</div> */}
|
<div className="info-content">{art.about}</div> */}
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="12">
|
<Col span="12">
|
||||||
{attributes &&
|
{attributes && (
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
<br />
|
<br />
|
||||||
<div className="info-header">Attributes</div>
|
<div className="info-header">Attributes</div>
|
||||||
<List
|
<List size="large" grid={{ column: 4 }}>
|
||||||
size="large"
|
{attributes.map(attribute => (
|
||||||
grid={{ column: 4 }}
|
|
||||||
>
|
|
||||||
{attributes.map(attribute =>
|
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Card title={attribute.trait_type}>{attribute.value}</Card>
|
<Card title={attribute.trait_type}>
|
||||||
|
{attribute.value}
|
||||||
|
</Card>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
20
js/yarn.lock
20
js/yarn.lock
|
@ -2211,26 +2211,6 @@
|
||||||
superstruct "^0.14.2"
|
superstruct "^0.14.2"
|
||||||
tweetnacl "^1.0.0"
|
tweetnacl "^1.0.0"
|
||||||
|
|
||||||
"@solana/web3.js@^1.24.1":
|
|
||||||
version "1.24.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.24.1.tgz#1fb29f344454669183206f452ab3b8792567cade"
|
|
||||||
integrity sha512-XImMWAvjcXteMQwe1FFjoe6u72xmcu+UYobPIxLEMX29XXWVTalyYRKBXvcOXwz6DliTYnFXmncNEwUDEFFHGg==
|
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.12.5"
|
|
||||||
"@solana/buffer-layout" "^3.0.0"
|
|
||||||
bn.js "^5.0.0"
|
|
||||||
borsh "^0.4.0"
|
|
||||||
bs58 "^4.0.1"
|
|
||||||
buffer "6.0.1"
|
|
||||||
crypto-hash "^1.2.2"
|
|
||||||
jayson "^3.4.4"
|
|
||||||
js-sha3 "^0.8.0"
|
|
||||||
node-fetch "^2.6.1"
|
|
||||||
rpc-websockets "^7.4.2"
|
|
||||||
secp256k1 "^4.0.2"
|
|
||||||
superstruct "^0.14.2"
|
|
||||||
tweetnacl "^1.0.0"
|
|
||||||
|
|
||||||
"@testing-library/dom@*", "@testing-library/dom@^7.28.1":
|
"@testing-library/dom@*", "@testing-library/dom@^7.28.1":
|
||||||
version "7.31.0"
|
version "7.31.0"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.0.tgz#938451abd3ca27e1b69bb395d4a40759fd7f5b3b"
|
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.0.tgz#938451abd3ca27e1b69bb395d4a40759fd7f5b3b"
|
||||||
|
|
|
@ -3,7 +3,10 @@ url = "https://anchor.projectserum.com"
|
||||||
|
|
||||||
[provider]
|
[provider]
|
||||||
cluster = "localnet"
|
cluster = "localnet"
|
||||||
wallet = "/Users/jprince/.config/solana/id.json"
|
wallet = "~/.config/solana/id.json"
|
||||||
|
|
||||||
|
[programs.mainnet]
|
||||||
|
nft_candy_machine = "cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ"
|
||||||
|
|
||||||
[scripts]
|
[scripts]
|
||||||
test = "mocha -t 1000000 tests/"
|
test = "mocha -t 1000000 tests/"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,8 +15,7 @@ cpi = ["no-entrypoint"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anchor-lang = "0.13.2"
|
anchor-lang = "0.14.0"
|
||||||
arrayref = "0.3.6"
|
arrayref = "0.3.6"
|
||||||
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
|
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
|
||||||
spl-token-metadata = { path = "../token-metadata/program", features = [ "no-entrypoint" ] }
|
spl-token-metadata = { path = "../token-metadata/program", features = [ "no-entrypoint" ] }
|
||||||
|
|
||||||
|
|
|
@ -351,6 +351,7 @@ pub mod nft_candy_machine {
|
||||||
position_from_right
|
position_from_right
|
||||||
);
|
);
|
||||||
if old_value_in_vec != data[my_position_in_vec] {
|
if old_value_in_vec != data[my_position_in_vec] {
|
||||||
|
msg!("Increasing count");
|
||||||
new_count = new_count
|
new_count = new_count
|
||||||
.checked_add(1)
|
.checked_add(1)
|
||||||
.ok_or(ErrorCode::NumericalOverflowError)?;
|
.ok_or(ErrorCode::NumericalOverflowError)?;
|
||||||
|
@ -395,7 +396,7 @@ pub mod nft_candy_machine {
|
||||||
}
|
}
|
||||||
|
|
||||||
if get_config_count(&ctx.accounts.config.to_account_info().data.borrow())?
|
if get_config_count(&ctx.accounts.config.to_account_info().data.borrow())?
|
||||||
!= candy_machine.data.items_available as usize
|
< candy_machine.data.items_available as usize
|
||||||
{
|
{
|
||||||
return Err(ErrorCode::ConfigLineMismatch.into());
|
return Err(ErrorCode::ConfigLineMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -430,7 +431,7 @@ pub struct InitializeCandyMachine<'info> {
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
#[instruction(data: ConfigData)]
|
#[instruction(data: ConfigData)]
|
||||||
pub struct InitializeConfig<'info> {
|
pub struct InitializeConfig<'info> {
|
||||||
#[account(mut, constraint= config.to_account_info().owner == program_id && config.to_account_info().data_len() > CONFIG_ARRAY_START+4+(data.max_number_of_lines as usize)*CONFIG_LINE_SIZE + 4 + (data.max_number_of_lines.checked_div(8).ok_or(ErrorCode::NumericalOverflowError)? as usize))]
|
#[account(mut, constraint= config.to_account_info().owner == program_id && config.to_account_info().data_len() >= CONFIG_ARRAY_START+4+(data.max_number_of_lines as usize)*CONFIG_LINE_SIZE + 4 + (data.max_number_of_lines.checked_div(8).ok_or(ErrorCode::NumericalOverflowError)? as usize))]
|
||||||
config: AccountInfo<'info>,
|
config: AccountInfo<'info>,
|
||||||
#[account(constraint= authority.data_is_empty() && authority.lamports() > 0 )]
|
#[account(constraint= authority.data_is_empty() && authority.lamports() > 0 )]
|
||||||
authority: AccountInfo<'info>,
|
authority: AccountInfo<'info>,
|
||||||
|
@ -450,7 +451,13 @@ pub struct AddConfigLines<'info> {
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct MintNFT<'info> {
|
pub struct MintNFT<'info> {
|
||||||
config: ProgramAccount<'info, Config>,
|
config: ProgramAccount<'info, Config>,
|
||||||
#[account(mut, has_one = config, has_one = wallet, seeds=[PREFIX.as_bytes(), config.key().as_ref(), candy_machine.data.uuid.as_bytes(), &[candy_machine.bump]])]
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = config,
|
||||||
|
has_one = wallet,
|
||||||
|
seeds = [PREFIX.as_bytes(), config.key().as_ref(), candy_machine.data.uuid.as_bytes()],
|
||||||
|
bump = candy_machine.bump,
|
||||||
|
)]
|
||||||
candy_machine: ProgramAccount<'info, CandyMachine>,
|
candy_machine: ProgramAccount<'info, CandyMachine>,
|
||||||
#[account(mut, signer)]
|
#[account(mut, signer)]
|
||||||
payer: AccountInfo<'info>,
|
payer: AccountInfo<'info>,
|
||||||
|
@ -480,7 +487,12 @@ pub struct MintNFT<'info> {
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct UpdateCandyMachine<'info> {
|
pub struct UpdateCandyMachine<'info> {
|
||||||
#[account(mut, has_one=authority, seeds=[PREFIX.as_bytes(), candy_machine.config.key().as_ref(), candy_machine.data.uuid.as_bytes(), &[candy_machine.bump]])]
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = authority,
|
||||||
|
seeds = [PREFIX.as_bytes(), candy_machine.config.key().as_ref(), candy_machine.data.uuid.as_bytes()],
|
||||||
|
bump = candy_machine.bump
|
||||||
|
)]
|
||||||
candy_machine: ProgramAccount<'info, CandyMachine>,
|
candy_machine: ProgramAccount<'info, CandyMachine>,
|
||||||
#[account(signer)]
|
#[account(signer)]
|
||||||
authority: AccountInfo<'info>,
|
authority: AccountInfo<'info>,
|
||||||
|
@ -610,6 +622,6 @@ pub enum ErrorCode {
|
||||||
CandyMachineEmpty,
|
CandyMachineEmpty,
|
||||||
#[msg("Candy machine is not live yet!")]
|
#[msg("Candy machine is not live yet!")]
|
||||||
CandyMachineNotLiveYet,
|
CandyMachineNotLiveYet,
|
||||||
#[msg("Number of config lines must match items available")]
|
#[msg("Number of config lines must be at least number of items available")]
|
||||||
ConfigLineMismatch,
|
ConfigLineMismatch,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ The Token Metadata Program's source is available on
|
||||||
[github](https://github.com/metaplex-foundation/metaplex)
|
[github](https://github.com/metaplex-foundation/metaplex)
|
||||||
|
|
||||||
There is also an example Rust client located at
|
There is also an example Rust client located at
|
||||||
[github](https://github.com/metaplex-foundation/metaplex/tree/master/token_metadata/test/src/main.rs)
|
[github](https://github.com/metaplex-foundation/metaplex/tree/master/rust/token-metadata/test/src/main.rs)
|
||||||
that can be perused for learning and run if desired with `cargo run --bin spl-token-metadata-test-client`. It allows testing out a variety of scenarios.
|
that can be perused for learning and run if desired with `cargo run --bin spl-token-metadata-test-client`. It allows testing out a variety of scenarios.
|
||||||
|
|
||||||
## Interface
|
## Interface
|
||||||
|
|
Loading…
Reference in New Issue