Merge branch 'master' of github.com:metaplex-foundation/metaplex into f

This commit is contained in:
Jordan Prince 2021-09-04 18:43:24 -05:00
commit db1a6980ee
17 changed files with 700 additions and 632 deletions

View File

@ -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:
https://www.notion.so/Metaplex-Developer-Guide-afefbc19841744c28587ab948a08cfac
https://docs.metaplex.com/
## Installing

View File

@ -52,7 +52,7 @@ yarn deploy
## 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:

View File

@ -21,7 +21,6 @@
},
"dependencies": {
"@project-serum/anchor": "^0.13.2",
"@solana/web3.js": "^1.24.1",
"arweave": "^1.10.16",
"bn.js": "^5.2.0",
"commander": "^8.1.0",

View File

@ -389,11 +389,12 @@ program
[],
'single',
);
console.info('transaction for arweave payment:', tx);
// data.append('tags', JSON.stringify(tags));
// payment transaction
const data = new FormData();
data.append('transaction', tx);
data.append('transaction', tx['txid']);
data.append('env', ENV);
data.append('file[]', fs.createReadStream(image), `image.png`);
data.append('file[]', manifestBuffer, 'metadata.json');
@ -432,9 +433,10 @@ program
}
}
const keys = Object.keys(cacheContent.items);
try {
await Promise.all(
chunks(Array.from(Array(images.length).keys()), 1000).map(
chunks(Array.from(Array(keys.length).keys()), 1000).map(
async allIndexesInSlice => {
for (
let offset = 0;
@ -443,30 +445,23 @@ program
) {
const indexes = allIndexesInSlice.slice(offset, offset + 10);
const onChain = indexes.filter(i => {
const index = images[i].replace(extension, '').split('/').pop();
return cacheContent.items[index].onChain;
const index = keys[i];
return cacheContent.items[index]?.onChain;
});
const ind = images[indexes[0]]
.replace(extension, '')
.split('/')
.pop();
const ind = keys[indexes[0]];
if (onChain.length != indexes.length) {
console.log(
'Writing indices ',
ind,
'-',
parseInt(ind) + indexes.length,
keys[indexes[indexes.length - 1]],
);
const txId = await anchorProgram.rpc.addConfigLines(
ind,
indexes.map(i => ({
uri: cacheContent.items[
images[i].replace(extension, '').split('/').pop()
].link,
name: cacheContent.items[
images[i].replace(extension, '').split('/').pop()
].name,
uri: cacheContent.items[keys[i]].link,
name: cacheContent.items[keys[i]].name,
})),
{
accounts: {
@ -477,12 +472,8 @@ program
},
);
indexes.forEach(i => {
cacheContent.items[
images[i].replace(extension, '').split('/').pop()
] = {
...cacheContent.items[
images[i].replace(extension, '').split('/').pop()
],
cacheContent.items[keys[i]] = {
...cacheContent.items[keys[i]],
onChain: true,
};
});
@ -610,12 +601,12 @@ program
},
);
console.log('Done');
console.log(`Done: CANDYMACHINE: ${candyMachine.toBase58()}`);
});
program
.command('mint_token_as_candy_machine_owner')
.option('-k, --keypair <path>', 'Solana wallet')
.command('mint_one_token')
.option('-k, --keypair <path>', `The purchaser's wallet key`)
.option('-c, --cache-name <path>', 'Cache file name')
.action(async (directory, cmd) => {
const solConnection = new anchor.web3.Connection(
@ -648,6 +639,7 @@ program
config,
cachedContent.program.uuid,
);
const candy = await anchorProgram.account.candyMachine.fetch(candyMachine);
const metadata = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
const tx = await anchorProgram.rpc.mintNft({
@ -655,7 +647,8 @@ program
config: config,
candyMachine: candyMachine,
payer: walletKey.publicKey,
wallet: walletKey.publicKey,
//@ts-ignore
wallet: candy.wallet,
mint: mint.publicKey,
metadata,
masterEdition,
@ -720,6 +713,8 @@ program
const config = await solConnection.getAccountInfo(
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);
for (let i = 0; i < keys.length; i++) {

View File

@ -1,3 +1,3 @@
REACT_APP_STORE_OWNER_ADDRESS_ADDRESS=CduMjFZLBeg3A9wMP3hQCoU1RQzzCpgSvQNXfCi1GCSB
REACT_APP_STORE_OWNER_ADDRESS_ADDRESS=
REACT_APP_STORE_ADDRESS=
REACT_APP_BIG_STORE=FALSE

View File

@ -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 };
}

View File

@ -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');
}
}

View File

@ -16,15 +16,12 @@ import {
sendTransactionsWithManualRetry,
MasterEditionV1,
MasterEditionV2,
findProgramAddress,
createAssociatedTokenAccountInstruction,
deprecatedMintNewEditionFromMasterEditionViaPrintingToken,
MetadataKey,
TokenAccountParser,
BidderMetadata,
getEditionMarkPda,
decodeEditionMarker,
BidStateType,
StringPublicKey,
toPublicKey,
WalletSigner,
@ -44,13 +41,13 @@ import {
PrizeTrackingTicket,
getPrizeTrackingTicket,
BidRedemptionTicket,
getBidRedemption,
} from '../models/metaplex';
import { claimBid } from '../models/metaplex/claimBid';
import { setupCancelBid } from './cancelBid';
import { deprecatedPopulateParticipationPrintingAccount } from '../models/metaplex/deprecatedPopulateParticipationPrintingAccount';
import { setupPlaceBid } from './sendPlaceBid';
import { claimUnusedPrizes } from './claimUnusedPrizes';
import { createMintAndAccountWithOne } from './createMintAndAccountWithOne';
import { BN } from 'bn.js';
import { QUOTE_MINT } from '../constants';
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(
connection: Connection,
auctionView: AuctionView,

View File

@ -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,
);
}

View File

@ -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>
)}
</>
);
};

View File

@ -1,21 +1,33 @@
import React from 'react';
import { Row, Col, Divider, Layout, Tag, Button, Skeleton, List, Card } from 'antd';
import React, { useState } from 'react';
import {
Row,
Col,
Divider,
Layout,
Tag,
Button,
Skeleton,
List,
Card,
} from 'antd';
import { useParams } from 'react-router-dom';
import { useArt, useExtendedArt } from './../../hooks';
import { useArt, useExtendedArt } from '../../hooks';
import { ArtContent } from '../../components/ArtContent';
import { shortenAddress, useConnection } from '@oyster/common';
import { useWallet } from '@solana/wallet-adapter-react';
import { MetaAvatar } from '../../components/MetaAvatar';
import { sendSignMetadata } from '../../actions/sendSignMetadata';
import { ViewOn } from './../../components/ViewOn';
import { ViewOn } from '../../components/ViewOn';
import { ArtType } from '../../types';
import { ArtMinting } from '../../components/ArtMinting';
const { Content } = Layout;
export const ArtView = () => {
const { id } = useParams<{ id: string }>();
const wallet = useWallet();
const [remountArtMinting, setRemountArtMinting] = useState(0);
const connection = useConnection();
const art = useArt(id);
@ -39,7 +51,7 @@ export const ArtView = () => {
const description = data?.description;
const attributes = data?.attributes;
const pubkey = wallet.publicKey?.toBase58() || '';
const pubkey = wallet?.publicKey?.toBase58() || '';
const tag = (
<div className="info-header">
@ -181,6 +193,13 @@ export const ArtView = () => {
>
Mark as Sold
</Button> */}
{/* TODO: Add conversion of MasterEditionV1 to MasterEditionV2 */}
<ArtMinting
id={id}
key={remountArtMinting}
onMint={async () => await setRemountArtMinting(prev => prev + 1)}
/>
</Col>
<Col span="12">
<Divider />
@ -197,23 +216,22 @@ export const ArtView = () => {
<div className="info-content">{art.about}</div> */}
</Col>
<Col span="12">
{attributes &&
{attributes && (
<>
<Divider />
<br />
<div className="info-header">Attributes</div>
<List
size="large"
grid={{ column: 4 }}
>
{attributes.map(attribute =>
<List size="large" grid={{ column: 4 }}>
{attributes.map(attribute => (
<List.Item>
<Card title={attribute.trait_type}>{attribute.value}</Card>
<Card title={attribute.trait_type}>
{attribute.value}
</Card>
</List.Item>
)}
))}
</List>
</>
}
)}
</Col>
</Row>
</Col>

View File

@ -2211,26 +2211,6 @@
superstruct "^0.14.2"
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":
version "7.31.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.0.tgz#938451abd3ca27e1b69bb395d4a40759fd7f5b3b"

View File

@ -3,7 +3,10 @@ url = "https://anchor.projectserum.com"
[provider]
cluster = "localnet"
wallet = "/Users/jprince/.config/solana/id.json"
wallet = "~/.config/solana/id.json"
[programs.mainnet]
nft_candy_machine = "cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ"
[scripts]
test = "mocha -t 1000000 tests/"

646
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,7 @@ cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = "0.13.2"
anchor-lang = "0.14.0"
arrayref = "0.3.6"
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
spl-token-metadata = { path = "../token-metadata/program", features = [ "no-entrypoint" ] }

View File

@ -351,6 +351,7 @@ pub mod nft_candy_machine {
position_from_right
);
if old_value_in_vec != data[my_position_in_vec] {
msg!("Increasing count");
new_count = new_count
.checked_add(1)
.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())?
!= candy_machine.data.items_available as usize
< candy_machine.data.items_available as usize
{
return Err(ErrorCode::ConfigLineMismatch.into());
}
@ -430,7 +431,7 @@ pub struct InitializeCandyMachine<'info> {
#[derive(Accounts)]
#[instruction(data: ConfigData)]
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>,
#[account(constraint= authority.data_is_empty() && authority.lamports() > 0 )]
authority: AccountInfo<'info>,
@ -450,7 +451,13 @@ pub struct AddConfigLines<'info> {
#[derive(Accounts)]
pub struct MintNFT<'info> {
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>,
#[account(mut, signer)]
payer: AccountInfo<'info>,
@ -480,7 +487,12 @@ pub struct MintNFT<'info> {
#[derive(Accounts)]
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>,
#[account(signer)]
authority: AccountInfo<'info>,
@ -610,6 +622,6 @@ pub enum ErrorCode {
CandyMachineEmpty,
#[msg("Candy machine is not live yet!")]
CandyMachineNotLiveYet,
#[msg("Number of config lines must match items available")]
#[msg("Number of config lines must be at least number of items available")]
ConfigLineMismatch,
}

View File

@ -16,7 +16,7 @@ The Token Metadata Program's source is available on
[github](https://github.com/metaplex-foundation/metaplex)
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.
## Interface