* Adding candy machine

* Still working on stuff

* Commit where is top using anchor

* Working on candy machine

* Continue working on candy machine

* Still working

* Completely reasonable failure

* Completely unreasonable failure

* Finally, we have uploading configs and tests

* Add uuid support to candy machines so candy machine can rerun with same config

* feat: init cli

* Got minting now bro. All downhill from here.

* All done

* Chang eaddress

* Minor fix to anchor settings

* feat: integrate candy

* feat: cli upload

* Heck yeah. Switch out the pda for a non pda to allow for alrger allocs

* Send authority to candy machine authority if retain authority is true

* feat: add add congif lines

* Forgotten conflict

* Rewire CLI to chunkify and add verification

* Current changes

* Client finished

* Fuckin bitmasks bro

* Fix metadata client

* Working on cli

* Fixed the healing loop

* here we go.

* Fix the error that caused price to be 1 instead of 5

Co-authored-by: bartosz-lipinski <264380+bartosz-lipinski@users.noreply.github.com>
This commit is contained in:
Jordan Prince 2021-08-31 14:16:16 -05:00 committed by GitHub
parent e1ab4494c4
commit f11659cb43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 5689 additions and 200 deletions

3
.gitignore vendored
View File

@ -1,5 +1,8 @@
node_modules/
.anchor
**/*.rs.bk
build/
rust/test/nft-candy-machine.js
dist/
lib/
deploy/

15
js/packages/cli/README.md Normal file
View File

@ -0,0 +1,15 @@
Format
* Folder with files named from 0-1.png
* JSON file with attributes, format
- Array with indices matching images
- Contains: title, description and array of traits ({"display_type":"number","trait_type":"generation","value":2})
Install dependencies
```
yarn
```

View File

@ -0,0 +1,35 @@
{
"name": "@metaplex/cli",
"version": "0.0.1",
"main": "./build/cli.js",
"license": "MIT",
"bin": {
"metaplex": "./build/cli.js"
},
"scripts": {
"build": "tsc -p ./src",
"watch": "tsc -w -p ./src",
"package:linux": "pkg . --no-bytecode --targets node14-linux-x64 --output bin/linux/metaplex",
"package:macos": "pkg . --no-bytecode --targets node14-macos-x64 --output bin/macos/metaplex",
"format": "prettier --loglevel warn --write \"**/*.{ts,js,json,yaml}\"",
"format:check": "prettier --loglevel warn --check \"**/*.{ts,js,json,yaml}\"",
"lint": "eslint \"src/**/*.ts\" --fix",
"lint:check": "eslint \"src/**/*.ts\""
},
"pkg": {
"scripts": "./build/**/*.js"
},
"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",
"form-data": "^4.0.0",
"node-fetch": "^2.6.1"
},
"devDependencies": {
"pkg": "^5.3.1",
"typescript": "^4.3.5"
}
}

761
js/packages/cli/src/cli.ts Executable file
View File

@ -0,0 +1,761 @@
#!/usr/bin/env node
import * as fs from 'fs';
import Arweave from 'arweave';
import * as path from 'path';
import fetch from 'node-fetch';
import FormData from 'form-data';
import { program } from 'commander';
import * as anchor from '@project-serum/anchor';
import BN from 'bn.js';
import { MintLayout, Token } from '@solana/spl-token';
import { sendTransactionWithRetryWithKeypair, fromUTF8Array } from './helper';
import {
LAMPORTS_PER_SOL,
PublicKey,
SystemProgram,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import { token } from '@project-serum/anchor/dist/utils';
const CACHE_PATH = './.cache';
const PAYMENT_WALLET = new anchor.web3.PublicKey(
'HvwC9QSAzvGXhhVrgPmauVwFWcYZhne3hVot9EbHuFTm',
);
const ENV = 'devnet';
const CANDY_MACHINE = 'candy_machine';
const programId = new anchor.web3.PublicKey(
'cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ',
);
const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
);
const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
);
const TOKEN_PROGRAM_ID = new PublicKey(
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
);
const getTokenWallet = async function (wallet: PublicKey, mint: PublicKey) {
return (
await PublicKey.findProgramAddress(
[wallet.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()],
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
)
)[0];
};
export function createAssociatedTokenAccountInstruction(
associatedTokenAddress: PublicKey,
payer: PublicKey,
walletAddress: PublicKey,
splTokenMintAddress: PublicKey,
) {
const keys = [
{
pubkey: payer,
isSigner: true,
isWritable: true,
},
{
pubkey: associatedTokenAddress,
isSigner: false,
isWritable: true,
},
{
pubkey: walletAddress,
isSigner: false,
isWritable: false,
},
{
pubkey: splTokenMintAddress,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{
pubkey: TOKEN_PROGRAM_ID,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
];
return new TransactionInstruction({
keys,
programId: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
data: Buffer.from([]),
});
}
function chunks(array, size) {
return Array.apply(0, new Array(Math.ceil(array.length / size))).map(
(_, index) => array.slice(index * size, (index + 1) * size),
);
}
const configArrayStart =
32 + // authority
4 +
6 + // uuid + u32 len
4 +
10 + // u32 len + symbol
2 + // seller fee basis points
1 +
4 +
5 * 34 + // optional + u32 len + actual vec
8 + //max supply
1 + //is mutable
1 + // retain authority
4; // max number of lines;
const configLineSize = 4 + 32 + 4 + 200;
program.version('0.0.1');
if (!fs.existsSync(CACHE_PATH)) {
fs.mkdirSync(CACHE_PATH);
}
const getCandyMachine = async (config: anchor.web3.PublicKey, uuid: string) => {
return await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(CANDY_MACHINE), config.toBuffer(), Buffer.from(uuid)],
programId,
);
};
const getConfig = async (authority: anchor.web3.PublicKey, uuid: string) => {
return await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(CANDY_MACHINE), authority.toBuffer(), Buffer.from(uuid)],
programId,
);
};
const getMetadata = async (
mint: anchor.web3.PublicKey,
): Promise<anchor.web3.PublicKey> => {
return (
await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from('metadata'),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
],
TOKEN_METADATA_PROGRAM_ID,
)
)[0];
};
const getMasterEdition = async (
mint: anchor.web3.PublicKey,
): Promise<anchor.web3.PublicKey> => {
return (
await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from('metadata'),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
Buffer.from('edition'),
],
TOKEN_METADATA_PROGRAM_ID,
)
)[0];
};
const createConfig = async function (
anchorProgram: anchor.Program,
payerWallet: anchor.web3.Keypair,
configData: {
maxNumberOfLines: BN;
symbol: string;
sellerFeeBasisPoints: number;
isMutable: boolean;
maxSupply: BN;
retainAuthority: boolean;
creators: {
address: anchor.web3.PublicKey;
verified: boolean;
share: number;
}[];
},
) {
const size =
configArrayStart +
4 +
configData.maxNumberOfLines.toNumber() * configLineSize +
4 +
Math.ceil(configData.maxNumberOfLines.toNumber() / 8);
const config = anchor.web3.Keypair.generate();
const uuid = config.publicKey.toBase58().slice(0, 6);
return {
config: config.publicKey,
uuid,
txId: await anchorProgram.rpc.initializeConfig(
{
uuid,
...configData,
},
{
accounts: {
config: config.publicKey,
authority: payerWallet.publicKey,
payer: payerWallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [payerWallet, config],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: payerWallet.publicKey,
newAccountPubkey: config.publicKey,
space: size,
lamports:
await anchorProgram.provider.connection.getMinimumBalanceForRentExemption(
size,
),
programId: programId,
}),
],
},
),
};
};
program
.command('upload')
.argument(
'<directory>',
'Directory containing images named from 0-n',
val => {
return fs.readdirSync(`${val}`).map(file => path.join(val, file));
},
)
.option(
'-u, --url',
'Solana cluster url',
'https://api.mainnet-beta.solana.com/',
)
.option('-k, --keypair <path>', 'Solana wallet')
// .argument('[second]', 'integer argument', (val) => parseInt(val), 1000)
.option('-s, --start-with', 'Image index to start with', '0')
.option('-n, --number', 'Number of images to upload', '10000')
.option('-c, --cache-name <path>', 'Cache file name')
.action(async (files: string[], options, cmd) => {
const extension = '.png';
const { startWith, keypair } = cmd.opts();
const cacheName = program.getOptionValue('cacheName') || 'temp';
const cachePath = path.join(CACHE_PATH, cacheName);
const savedContent = fs.existsSync(cachePath)
? JSON.parse(fs.readFileSync(cachePath).toString())
: undefined;
const cacheContent = savedContent || {};
if (!cacheContent.program) {
cacheContent.program = {};
}
let existingInCache = [];
if (!cacheContent.items) {
cacheContent.items = {};
} else {
existingInCache = Object.keys(cacheContent.items);
}
const seen = {};
const newFiles = [];
files.forEach(f => {
if (!seen[f.replace(extension, '').split('/').pop()]) {
seen[f.replace(extension, '').split('/').pop()] = true;
newFiles.push(f);
}
});
existingInCache.forEach(f => {
if (!seen[f]) {
seen[f] = true;
newFiles.push(f + '.png');
}
});
const images = newFiles.filter(val => path.extname(val) === extension);
const SIZE = images.length; // images.length;
const walletKey = anchor.web3.Keypair.fromSecretKey(
new Uint8Array(JSON.parse(fs.readFileSync(keypair).toString())),
);
// const conversionRates = JSON.parse(
// await (
// await fetch(
// 'https://api.coingecko.com/api/v3/simple/price?ids=solana,arweave&vs_currencies=usd',
// )
// ).text(),
// );
// const baseCost = fetch(``);
// const increment = fetch(``);
const solConnection = new anchor.web3.Connection(
`https://api.${ENV}.solana.com/`,
);
const walletWrapper = new anchor.Wallet(walletKey);
const provider = new anchor.Provider(solConnection, walletWrapper, {
preflightCommitment: 'recent',
});
const idl = await anchor.Program.fetchIdl(programId, provider);
const anchorProgram = new anchor.Program(idl, programId, provider);
let config = cacheContent.program.config
? new anchor.web3.PublicKey(cacheContent.program.config)
: undefined;
const block = await solConnection.getRecentBlockhash();
for (let i = 0; i < SIZE; i++) {
const image = images[i];
const imageName = path.basename(image);
const index = imageName.replace(extension, '');
console.log(`Processing file: ${index}`);
const storageCost = 10;
let link = cacheContent?.items?.[index]?.link;
if (!link || !cacheContent.program.uuid) {
const imageBuffer = Buffer.from(fs.readFileSync(image));
const manifestPath = image.replace(extension, '.json');
const manifestContent = fs
.readFileSync(manifestPath)
.toString()
.replace(imageName, 'image.png')
.replace(imageName, 'image.png');
const manifest = JSON.parse(manifestContent);
const manifestBuffer = Buffer.from(JSON.stringify(manifest));
const sizeInBytes = imageBuffer.length + manifestBuffer.length;
if (i === 0 && !cacheContent.program.uuid) {
// initialize config
try {
const res = await createConfig(anchorProgram, walletKey, {
maxNumberOfLines: new BN(SIZE),
symbol: manifest.symbol,
sellerFeeBasisPoints: manifest.seller_fee_basis_points,
isMutable: true,
maxSupply: new BN(0),
retainAuthority: true,
creators: manifest.properties.creators.map(creator => {
return {
address: new anchor.web3.PublicKey(creator.address),
verified: false,
share: creator.share,
};
}),
});
cacheContent.program.uuid = res.uuid;
cacheContent.program.config = res.config.toBase58();
config = res.config;
fs.writeFileSync(
path.join(CACHE_PATH, cacheName),
JSON.stringify(cacheContent),
);
} catch (exx) {
console.error('Error deploying config to Solana network.', exx);
// console.error(exx);
}
}
if (!link) {
let instructions = [
anchor.web3.SystemProgram.transfer({
fromPubkey: walletKey.publicKey,
toPubkey: PAYMENT_WALLET,
lamports: storageCost,
}),
];
const tx = await sendTransactionWithRetryWithKeypair(
solConnection,
walletKey,
instructions,
[],
'single',
);
// data.append('tags', JSON.stringify(tags));
// payment transaction
const data = new FormData();
data.append('transaction', tx);
data.append('env', ENV);
data.append('file[]', fs.createReadStream(image), `image.png`);
data.append('file[]', manifestBuffer, 'metadata.json');
try {
const result = await (
await fetch(
'https://us-central1-principal-lane-200702.cloudfunctions.net/uploadFile3',
{
method: 'POST',
body: data,
},
)
).json();
const metadataFile = result.messages?.find(
m => m.filename === 'manifest.json',
);
if (metadataFile?.transactionId) {
link = `https://arweave.net/${metadataFile.transactionId}`;
console.log(`File uploaded: ${link}`);
}
cacheContent.items[index] = {
link,
name: manifest.name,
onChain: false,
};
fs.writeFileSync(
path.join(CACHE_PATH, cacheName),
JSON.stringify(cacheContent),
);
} catch (er) {
console.error(`Error uploading file ${index}`, er);
}
}
}
}
try {
await Promise.all(
chunks(Array.from(Array(images.length).keys()), 1000).map(
async allIndexesInSlice => {
for (
let offset = 0;
offset < allIndexesInSlice.length;
offset += 10
) {
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 ind = images[indexes[0]]
.replace(extension, '')
.split('/')
.pop();
if (onChain.length != indexes.length) {
console.log(
'Writing indices ',
ind,
'-',
parseInt(ind) + indexes.length,
);
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,
})),
{
accounts: {
config,
authority: walletKey.publicKey,
},
signers: [walletKey],
},
);
indexes.forEach(i => {
cacheContent.items[
images[i].replace(extension, '').split('/').pop()
] = {
...cacheContent.items[
images[i].replace(extension, '').split('/').pop()
],
onChain: true,
};
});
fs.writeFileSync(
path.join(CACHE_PATH, cacheName),
JSON.stringify(cacheContent),
);
}
}
},
),
);
} catch (e) {
console.error(e);
} finally {
fs.writeFileSync(
path.join(CACHE_PATH, cacheName),
JSON.stringify(cacheContent),
);
}
console.log('Done');
// TODO: start candy machine
});
program
.command('set_start_date')
.option('-k, --keypair <path>', 'Solana wallet')
.option('-c, --cache-name <path>', 'Cache file name')
.option('-d, --date <string>', 'timestamp - eg "04 Dec 1995 00:12:00 GMT"')
.action(async (directory, cmd) => {
const solConnection = new anchor.web3.Connection(
`https://api.${ENV}.solana.com/`,
);
const { keypair } = cmd.opts();
const cacheName = cmd.getOptionValue('cacheName') || 'temp';
const cachePath = path.join(CACHE_PATH, cacheName);
const cachedContent = fs.existsSync(cachePath)
? JSON.parse(fs.readFileSync(cachePath).toString())
: undefined;
const date = cmd.getOptionValue('date');
const secondsSinceEpoch = (date ? Date.parse(date) : Date.now()) / 1000;
const walletKey = anchor.web3.Keypair.fromSecretKey(
new Uint8Array(JSON.parse(fs.readFileSync(keypair).toString())),
);
const walletWrapper = new anchor.Wallet(walletKey);
const provider = new anchor.Provider(solConnection, walletWrapper, {
preflightCommitment: 'recent',
});
const idl = await anchor.Program.fetchIdl(programId, provider);
const anchorProgram = new anchor.Program(idl, programId, provider);
const [candyMachine, _] = await getCandyMachine(
new anchor.web3.PublicKey(cachedContent.program.config),
cachedContent.program.uuid,
);
const tx = await anchorProgram.rpc.updateCandyMachine(
null,
new anchor.BN(secondsSinceEpoch),
{
accounts: {
candyMachine,
authority: walletKey.publicKey,
},
},
);
console.log('Done', secondsSinceEpoch, tx);
});
program
.command('create_candy_machine')
.option('-k, --keypair <path>', 'Solana wallet')
.option('-c, --cache-name <path>', 'Cache file name')
.option('-p, --price <string>', 'SOL price')
.action(async (directory, cmd) => {
const solConnection = new anchor.web3.Connection(
`https://api.${ENV}.solana.com/`,
);
const { keypair } = cmd.opts();
const solPriceStr = cmd.getOptionValue('price') || '1';
const lamports = parseInt(solPriceStr) * LAMPORTS_PER_SOL;
const cacheName = program.getOptionValue('cacheName') || 'temp';
const cachePath = path.join(CACHE_PATH, cacheName);
const cachedContent = fs.existsSync(cachePath)
? JSON.parse(fs.readFileSync(cachePath).toString())
: undefined;
const walletKey = anchor.web3.Keypair.fromSecretKey(
new Uint8Array(JSON.parse(fs.readFileSync(keypair).toString())),
);
const walletWrapper = new anchor.Wallet(walletKey);
const provider = new anchor.Provider(solConnection, walletWrapper, {
preflightCommitment: 'recent',
});
const idl = await anchor.Program.fetchIdl(programId, provider);
const anchorProgram = new anchor.Program(idl, programId, provider);
const config = new anchor.web3.PublicKey(cachedContent.program.config);
const [candyMachine, bump] = await getCandyMachine(
config,
cachedContent.program.uuid,
);
const tx = await anchorProgram.rpc.initializeCandyMachine(
bump,
{
uuid: cachedContent.program.uuid,
price: new anchor.BN(lamports),
itemsAvailable: new anchor.BN(Object.keys(cachedContent.items).length),
goLiveDate: null,
},
{
accounts: {
candyMachine,
wallet: walletKey.publicKey,
config: config,
authority: walletKey.publicKey,
payer: walletKey.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [],
},
);
console.log('Done');
});
program
.command('mint_token_as_candy_machine_owner')
.option('-k, --keypair <path>', 'Solana wallet')
.option('-c, --cache-name <path>', 'Cache file name')
.action(async (directory, cmd) => {
const solConnection = new anchor.web3.Connection(
`https://api.${ENV}.solana.com/`,
);
const { keypair } = cmd.opts();
const solPriceStr = program.getOptionValue('price') || '1';
const lamports = parseInt(solPriceStr) * LAMPORTS_PER_SOL;
const cacheName = program.getOptionValue('cacheName') || 'temp';
const cachePath = path.join(CACHE_PATH, cacheName);
const cachedContent = fs.existsSync(cachePath)
? JSON.parse(fs.readFileSync(cachePath).toString())
: undefined;
const mint = anchor.web3.Keypair.generate();
const walletKey = anchor.web3.Keypair.fromSecretKey(
new Uint8Array(JSON.parse(fs.readFileSync(keypair).toString())),
);
const token = await getTokenWallet(walletKey.publicKey, mint.publicKey);
const walletWrapper = new anchor.Wallet(walletKey);
const provider = new anchor.Provider(solConnection, walletWrapper, {
preflightCommitment: 'recent',
});
const idl = await anchor.Program.fetchIdl(programId, provider);
const anchorProgram = new anchor.Program(idl, programId, provider);
const config = new anchor.web3.PublicKey(cachedContent.program.config);
const [candyMachine, bump] = await getCandyMachine(
config,
cachedContent.program.uuid,
);
const metadata = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
const tx = await anchorProgram.rpc.mintNft({
accounts: {
config: config,
candyMachine: candyMachine,
payer: walletKey.publicKey,
wallet: walletKey.publicKey,
mint: mint.publicKey,
metadata,
masterEdition,
mintAuthority: walletKey.publicKey,
updateAuthority: walletKey.publicKey,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
signers: [mint, walletKey],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: walletKey.publicKey,
newAccountPubkey: mint.publicKey,
space: MintLayout.span,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span,
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
0,
walletKey.publicKey,
walletKey.publicKey,
),
createAssociatedTokenAccountInstruction(
token,
walletKey.publicKey,
walletKey.publicKey,
mint.publicKey,
),
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
token,
walletKey.publicKey,
[],
1,
),
],
});
console.log('Done', tx);
});
program
.command('verify')
.option('-c, --cache-name <path>', 'Cache file name')
.action(async (directory, second, options) => {
const solConnection = new anchor.web3.Connection(
`https://api.${ENV}.solana.com/`,
);
const cacheName = program.getOptionValue('cacheName') || 'temp';
const cachePath = path.join(CACHE_PATH, cacheName);
const cachedContent = fs.existsSync(cachePath)
? JSON.parse(fs.readFileSync(cachePath).toString())
: undefined;
const config = await solConnection.getAccountInfo(
new PublicKey(cachedContent.program.config),
);
const keys = Object.keys(cachedContent.items);
for (let i = 0; i < keys.length; i++) {
console.log('Looking at key ', i);
const key = keys[i];
const thisSlice = config.data.slice(
configArrayStart + 4 + configLineSize * i,
configArrayStart + 4 + configLineSize * (i + 1),
);
const name = fromUTF8Array([...thisSlice.slice(4, 36)]);
const uri = fromUTF8Array([...thisSlice.slice(40, 240)]);
const cacheItem = cachedContent.items[key];
if (!name.match(cacheItem.name) || !uri.match(cacheItem.link)) {
console.log(
'Name',
name,
'or uri',
uri,
'didnt match cache values of',
cacheItem.name,
'and',
cacheItem.link,
' marking to rerun for image',
key,
);
cacheItem.onChain = false;
} else {
console.log('Name', name, 'with', uri, 'checked out');
}
}
fs.writeFileSync(
path.join(CACHE_PATH, cacheName),
JSON.stringify(cachedContent),
);
});
program.command('find-wallets').action(() => {});
program.parse(process.argv);

View File

@ -0,0 +1,317 @@
// const createPaymentTransaction = () => {
// // TODO:
// const tx = new Transaction();
// tx.add(SystemProgram.transfer({
// fromPubkey: walletKey.publicKey,
// toPubkey: PAYMENT_WALLET,
// lamports: storageCost,
// }));
import {
Connection,
Keypair,
TransactionInstruction,
Commitment,
Transaction,
RpcResponseAndContext,
SignatureStatus,
SimulatedTransactionResponse,
TransactionSignature,
FeeCalculator,
Blockhash,
} from '@solana/web3.js';
interface BlockhashAndFeeCalculator {
blockhash: Blockhash;
feeCalculator: FeeCalculator;
}
export const sendTransactionWithRetryWithKeypair = async (
connection: Connection,
wallet: Keypair,
instructions: TransactionInstruction[],
signers: Keypair[],
commitment: Commitment = 'singleGossip',
includesFeePayer: boolean = false,
block?: BlockhashAndFeeCalculator,
beforeSend?: () => void,
) => {
let transaction = new Transaction();
instructions.forEach(instruction => transaction.add(instruction));
transaction.recentBlockhash = (
block || (await connection.getRecentBlockhash(commitment))
).blockhash;
if (includesFeePayer) {
transaction.setSigners(...signers.map(s => s.publicKey));
} else {
transaction.setSigners(
// fee payed by the wallet owner
wallet.publicKey,
...signers.map(s => s.publicKey),
);
}
if (signers.length > 0) {
transaction.partialSign(...signers);
}
transaction.sign(wallet);
if (beforeSend) {
beforeSend();
}
const { txid, slot } = await sendSignedTransaction({
connection,
signedTransaction: transaction,
});
return { txid, slot };
};
export const getUnixTs = () => {
return new Date().getTime() / 1000;
};
const DEFAULT_TIMEOUT = 15000;
export async function sendSignedTransaction({
signedTransaction,
connection,
timeout = DEFAULT_TIMEOUT,
}: {
signedTransaction: Transaction;
connection: Connection;
sendingMessage?: string;
sentMessage?: string;
successMessage?: string;
timeout?: number;
}): Promise<{ txid: string; slot: number }> {
const rawTransaction = signedTransaction.serialize();
const startTime = getUnixTs();
let slot = 0;
const txid: TransactionSignature = await connection.sendRawTransaction(
rawTransaction,
{
skipPreflight: true,
},
);
console.log('Started awaiting confirmation for', txid);
let done = false;
(async () => {
while (!done && getUnixTs() - startTime < timeout) {
connection.sendRawTransaction(rawTransaction, {
skipPreflight: true,
});
await sleep(500);
}
})();
try {
const confirmation = await awaitTransactionSignatureConfirmation(
txid,
timeout,
connection,
'recent',
true,
);
if (!confirmation)
throw new Error('Timed out awaiting confirmation on transaction');
if (confirmation.err) {
console.error(confirmation.err);
throw new Error('Transaction failed: Custom instruction error');
}
slot = confirmation?.slot || 0;
} catch (err) {
console.error('Timeout Error caught', err);
if (err.timeout) {
throw new Error('Timed out awaiting confirmation on transaction');
}
let simulateResult: SimulatedTransactionResponse | null = null;
try {
simulateResult = (
await simulateTransaction(connection, signedTransaction, 'single')
).value;
} catch (e) {}
if (simulateResult && simulateResult.err) {
if (simulateResult.logs) {
for (let i = simulateResult.logs.length - 1; i >= 0; --i) {
const line = simulateResult.logs[i];
if (line.startsWith('Program log: ')) {
throw new Error(
'Transaction failed: ' + line.slice('Program log: '.length),
);
}
}
}
throw new Error(JSON.stringify(simulateResult.err));
}
// throw new Error('Transaction failed');
} finally {
done = true;
}
console.log('Latency', txid, getUnixTs() - startTime);
return { txid, slot };
}
async function simulateTransaction(
connection: Connection,
transaction: Transaction,
commitment: Commitment,
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
// @ts-ignore
transaction.recentBlockhash = await connection._recentBlockhash(
// @ts-ignore
connection._disableBlockhashCaching,
);
const signData = transaction.serializeMessage();
// @ts-ignore
const wireTransaction = transaction._serialize(signData);
const encodedTransaction = wireTransaction.toString('base64');
const config: any = { encoding: 'base64', commitment };
const args = [encodedTransaction, config];
// @ts-ignore
const res = await connection._rpcRequest('simulateTransaction', args);
if (res.error) {
throw new Error('failed to simulate transaction: ' + res.error.message);
}
return res.result;
}
async function awaitTransactionSignatureConfirmation(
txid: TransactionSignature,
timeout: number,
connection: Connection,
commitment: Commitment = 'recent',
queryStatus = false,
): Promise<SignatureStatus | null | void> {
let done = false;
let status: SignatureStatus | null | void = {
slot: 0,
confirmations: 0,
err: null,
};
let subId = 0;
status = await new Promise(async (resolve, reject) => {
setTimeout(() => {
if (done) {
return;
}
done = true;
console.log('Rejecting for timeout...');
reject({ timeout: true });
}, timeout);
try {
subId = connection.onSignature(
txid,
(result, context) => {
done = true;
status = {
err: result.err,
slot: context.slot,
confirmations: 0,
};
if (result.err) {
console.log('Rejected via websocket', result.err);
reject(status);
} else {
console.log('Resolved via websocket', result);
resolve(status);
}
},
commitment,
);
} catch (e) {
done = true;
console.error('WS error in setup', txid, e);
}
while (!done && queryStatus) {
// eslint-disable-next-line no-loop-func
(async () => {
try {
const signatureStatuses = await connection.getSignatureStatuses([
txid,
]);
status = signatureStatuses && signatureStatuses.value[0];
if (!done) {
if (!status) {
console.log('REST null result for', txid, status);
} else if (status.err) {
console.log('REST error for', txid, status);
done = true;
reject(status.err);
} else if (!status.confirmations) {
console.log('REST no confirmations for', txid, status);
} else {
console.log('REST confirmation for', txid, status);
done = true;
resolve(status);
}
}
} catch (e) {
if (!done) {
console.log('REST connection error: txid', txid, e);
}
}
})();
await sleep(2000);
}
});
//@ts-ignore
if (connection._signatureSubscriptions[subId])
connection.removeSignatureListener(subId);
done = true;
console.log('Returning status', status);
return status;
}
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function fromUTF8Array(data: number[]) {
// array of bytes
let str = '',
i;
for (i = 0; i < data.length; i++) {
const value = data[i];
if (value < 0x80) {
str += String.fromCharCode(value);
} else if (value > 0xbf && value < 0xe0) {
str += String.fromCharCode(((value & 0x1f) << 6) | (data[i + 1] & 0x3f));
i += 1;
} else if (value > 0xdf && value < 0xf0) {
str += String.fromCharCode(
((value & 0x0f) << 12) |
((data[i + 1] & 0x3f) << 6) |
(data[i + 2] & 0x3f),
);
i += 2;
} else {
// surrogate pair
const charCode =
(((value & 0x07) << 18) |
((data[i + 1] & 0x3f) << 12) |
((data[i + 2] & 0x3f) << 6) |
(data[i + 3] & 0x3f)) -
0x010000;
str += String.fromCharCode(
(charCode >> 10) | 0xd800,
(charCode & 0x03ff) | 0xdc00,
);
i += 3;
}
}
return str;
}

View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "./../build",
"declaration": false,
"esModuleInterop": true,
"noImplicitAny": false,
"removeComments": false,
"isolatedModules": false,
"experimentalDecorators": true,
"downlevelIteration": true,
"emitDecoratorMetadata": true,
"noLib": false,
"preserveConstEnums": true,
"suppressImplicitAnyIndexErrors": true,
"lib": ["dom", "es6"]
},
"exclude": ["node_modules", "typings/browser", "typings/browser.d.ts"],
"atom": {
"rewriteTsconfig": false
}
}

View File

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

View File

@ -19,6 +19,7 @@ import {
BidderMetadata,
MAX_METADATA_LEN,
MAX_EDITION_LEN,
placeBid,
useWalletModal,
} from '@oyster/common';
import { useWallet } from '@solana/wallet-adapter-react';

View File

@ -237,6 +237,11 @@
dependencies:
"@babel/types" "^7.12.13"
"@babel/helper-validator-identifier@^7.12.11":
version "7.14.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48"
integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==
"@babel/helper-validator-identifier@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
@ -279,6 +284,11 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@7.13.13":
version "7.13.13"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.13.tgz#42f03862f4aed50461e543270916b47dd501f0df"
integrity sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==
"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.14.2", "@babel/parser@^7.14.3", "@babel/parser@^7.4.3":
version "7.14.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.4.tgz#a5c560d6db6cd8e6ed342368dea8039232cbab18"
@ -380,6 +390,15 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@7.13.12":
version "7.13.12"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.12.tgz#edbf99208ef48852acdff1c8a681a1e4ade580cd"
integrity sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@babel/types@7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
@ -2006,6 +2025,26 @@
snake-case "^3.0.4"
toml "^3.0.0"
"@project-serum/anchor@^0.13.2":
version "0.13.2"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.13.2.tgz#c6c9a15720cbecd0f79bab56a0690d156fd06f8a"
integrity sha512-xfjzHBy8ZvJuK1EYAba7+CT6TmJ+UWXmGGm915J0IFJTwGwYT4+bDa/qZF7EnZ6trYxg7owEOMLfvF7Jp+WC0w==
dependencies:
"@project-serum/borsh" "^0.2.2"
"@solana/web3.js" "^1.17.0"
base64-js "^1.5.1"
bn.js "^5.1.2"
bs58 "^4.0.1"
buffer-layout "^1.2.0"
camelcase "^5.3.1"
crypto-hash "^1.3.0"
eventemitter3 "^4.0.7"
find "^0.3.0"
js-sha256 "^0.9.0"
pako "^2.0.3"
snake-case "^3.0.4"
toml "^3.0.0"
"@project-serum/borsh@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.2.tgz#63e558f2d6eb6ab79086bf499dea94da3182498f"
@ -2172,6 +2211,26 @@
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"
@ -3096,6 +3155,13 @@ agent-base@4, agent-base@^4.3.0:
dependencies:
es6-promisify "^5.0.0"
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
agent-base@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
@ -3292,6 +3358,13 @@ aproba@^2.0.0:
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
arconnect@^0.2.8:
version "0.2.9"
resolved "https://registry.yarnpkg.com/arconnect/-/arconnect-0.2.9.tgz#be07d2281f20d864a91cdcb44e3eed7fccb97f12"
integrity sha512-Us49eN/+8l6BrkAPdXnJVPwWlxxUPR7QaBjA0j3OBAcioIFRpwTdoPN9FxtwDGN91lgM6ebOudTXJToRiNizoA==
dependencies:
arweave "^1.10.13"
are-we-there-yet@~1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
@ -3402,12 +3475,23 @@ arweave-deploy@^1.9.1:
resolved "https://registry.yarnpkg.com/arweave-deploy/-/arweave-deploy-1.9.1.tgz#1cc45fc5eb2fd62985ad4e54076ffe850a6a33f7"
integrity sha512-yNC9joASgsqQBASLwNivzMJxUHyXuzWHg7qLIGg/+y5hDprSy3fsMUNJ83qCpATYnmJdnECcBXKESBnhalM88w==
arweave@^1.10.13, arweave@^1.10.16:
version "1.10.16"
resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.10.16.tgz#b968590cb413242636196d2339562dd106840bb9"
integrity sha512-j2UM7C/2MHO7Mv7wbzhihgVs8uN2+QltA+3cYaM82dNNEBBJylIClJUHHMqsK/2ejCnziQm4FofiUFbsQDdQDg==
dependencies:
arconnect "^0.2.8"
asn1.js "^5.4.1"
axios "^0.21.1"
base64-js "^1.3.1"
bignumber.js "^9.0.1"
asap@^2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
asn1.js@^5.2.0:
asn1.js@^5.2.0, asn1.js@^5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
@ -3530,6 +3614,11 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
at-least-node@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
atob-lite@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696"
@ -3583,6 +3672,13 @@ axios@^0.18.0:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@ -4289,6 +4385,15 @@ bip66@^1.1.5:
dependencies:
safe-buffer "^5.0.1"
bl@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
blakejs@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5"
@ -4606,7 +4711,7 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.2.1, buffer@^5.4.3:
buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
@ -4976,6 +5081,15 @@ cliui@^5.0.0:
strip-ansi "^5.2.0"
wrap-ansi "^5.1.0"
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
clone-deep@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
@ -5071,7 +5185,7 @@ columnify@^1.5.4:
strip-ansi "^3.0.0"
wcwidth "^1.0.0"
combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -5093,6 +5207,11 @@ commander@^6.2.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commander@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.1.0.tgz#db36e3e66edf24ff591d639862c6ab2c52664362"
integrity sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -5651,6 +5770,13 @@ debug@3.1.0, debug@=3.1.0:
dependencies:
ms "2.0.0"
debug@4:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@ -5695,6 +5821,13 @@ decompress-response@^3.3.0:
dependencies:
mimic-response "^1.0.0"
decompress-response@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
dependencies:
mimic-response "^2.0.0"
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
@ -5719,6 +5852,11 @@ deep-equal@^1.0.1, deep-equal@~1.1.1:
object-keys "^1.1.1"
regexp.prototype.flags "^1.2.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@ -5848,6 +5986,11 @@ detect-indent@^5.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
detect-newline@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
@ -6125,7 +6268,7 @@ encoding@0.1.13, encoding@^0.1.11:
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.4:
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@ -6274,6 +6417,18 @@ escodegen@^1.9.1:
optionalDependencies:
source-map "~0.6.1"
escodegen@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==
dependencies:
esprima "^4.0.1"
estraverse "^5.2.0"
esutils "^2.0.2"
optionator "^0.8.1"
optionalDependencies:
source-map "~0.6.1"
eslint-config-prettier@^6.15.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz#7f93f6cb7d45a92f1537a70ecc06366e1ac6fed9"
@ -6974,6 +7129,11 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
expect@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca"
@ -7361,6 +7521,11 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
follow-redirects@^1.10.0:
version "1.14.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b"
integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==
for-each@^0.3.3, for-each@~0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@ -7383,6 +7548,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@ -7416,7 +7590,7 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
from2@^2.1.0:
from2@^2.1.0, from2@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
@ -7424,6 +7598,11 @@ from2@^2.1.0:
inherits "^2.0.1"
readable-stream "^2.0.0"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@ -7433,6 +7612,16 @@ fs-extra@^8.1.0:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
dependencies:
at-least-node "^1.0.0"
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-minipass@^1.2.5:
version "1.2.7"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
@ -7507,7 +7696,7 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.1:
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@ -7668,6 +7857,11 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@ -8095,6 +8289,14 @@ https-proxy-agent@^2.2.3:
agent-base "^4.3.0"
debug "^3.1.0"
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@ -8268,7 +8470,7 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.2, ini@^1.3.4:
ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
@ -8338,6 +8540,14 @@ interpret@^1.0.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
into-stream@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702"
integrity sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==
dependencies:
from2 "^2.3.0"
p-is-promise "^3.0.0"
invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@ -9438,6 +9648,15 @@ jsonfile@^4.0.0:
optionalDependencies:
graceful-fs "^4.1.6"
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@ -10235,6 +10454,11 @@ mimic-response@^1.0.0:
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
mimic-response@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
@ -10289,7 +10513,7 @@ minimist-options@^3.0.1:
arrify "^1.0.1"
is-plain-obj "^1.1.0"
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5:
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@~1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@ -10333,6 +10557,11 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp-promise@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1"
@ -10417,6 +10646,14 @@ multimatch@^3.0.0:
arrify "^1.0.1"
minimatch "^3.0.4"
multistream@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/multistream/-/multistream-4.1.0.tgz#7bf00dfd119556fbc153cff3de4c6d477909f5a8"
integrity sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==
dependencies:
once "^1.4.0"
readable-stream "^3.6.0"
mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
@ -10468,6 +10705,11 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
native-request@^1.0.5:
version "1.0.8"
resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.8.tgz#8f66bf606e0f7ea27c0e5995eb2f5d03e33ae6fb"
@ -10590,6 +10832,13 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
node-abi@^2.7.0:
version "2.30.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b"
integrity sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==
dependencies:
semver "^5.4.1"
node-addon-api@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
@ -10711,6 +10960,11 @@ node-releases@^1.1.71:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
noop-logger@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=
nopt@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
@ -10864,7 +11118,7 @@ npm-run-path@^4.0.0:
dependencies:
path-key "^3.0.0"
npmlog@^4.1.2:
npmlog@^4.0.1, npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@ -11122,6 +11376,11 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
p-is-promise@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971"
integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==
p-limit@3.1.0, p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
@ -11529,6 +11788,40 @@ pkg-dir@^5.0.0:
dependencies:
find-up "^5.0.0"
pkg-fetch@3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/pkg-fetch/-/pkg-fetch-3.2.2.tgz#33f391eb176c1844e93189a32f2279b36a1ec949"
integrity sha512-bLhFNT4cNnONxzbHo1H2mCCKuQkCR4dgQtv0gUZnWtp8TDP0v0UAXKHG7DXhAoTC5IYP3slLsFJtIda9ksny8g==
dependencies:
chalk "^4.1.0"
fs-extra "^9.1.0"
https-proxy-agent "^5.0.0"
node-fetch "^2.6.1"
progress "^2.0.3"
semver "^7.3.5"
yargs "^16.2.0"
pkg@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/pkg/-/pkg-5.3.1.tgz#8f81671613b9e5bb1d83c39b2eed4799e1e679fe"
integrity sha512-jT/sptM1ZG++FNk+jnJYNoWLDQXYd7hqpnBhd5j18SNW1jJzNYo55RahuCiD0KN0PX9mb53GWCqKM0ia/mJytA==
dependencies:
"@babel/parser" "7.13.13"
"@babel/types" "7.13.12"
chalk "^4.1.0"
escodegen "^2.0.0"
fs-extra "^9.1.0"
globby "^11.0.3"
into-stream "^6.0.0"
minimist "^1.2.5"
multistream "^4.1.0"
pkg-fetch "3.2.2"
prebuild-install "6.0.1"
progress "^2.0.3"
resolve "^1.20.0"
stream-meter "^1.0.4"
tslib "2.1.0"
platform@1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
@ -11605,6 +11898,27 @@ preact@^10.5.9:
resolved "https://registry.yarnpkg.com/preact/-/preact-10.5.13.tgz#85f6c9197ecd736ce8e3bec044d08fd1330fa019"
integrity sha512-q/vlKIGNwzTLu+jCcvywgGrt+H/1P/oIRSD6mV4ln3hmlC+Aa34C7yfPI4+5bzW8pONyVXYS7SvXosy2dKKtWQ==
prebuild-install@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.0.1.tgz#5902172f7a40eb67305b96c2a695db32636ee26d"
integrity sha512-7GOJrLuow8yeiyv75rmvZyeMGzl8mdEX5gY69d6a6bHWmiPevwqFw+tQavhK0EYMaSg3/KD24cWqeQv1EWsqDQ==
dependencies:
detect-libc "^1.0.3"
expand-template "^2.0.3"
github-from-package "0.0.0"
minimist "^1.2.3"
mkdirp-classic "^0.5.3"
napi-build-utils "^1.0.1"
node-abi "^2.7.0"
noop-logger "^0.1.1"
npmlog "^4.0.1"
pump "^3.0.0"
rc "^1.2.7"
simple-get "^3.0.3"
tar-fs "^2.0.0"
tunnel-agent "^0.6.0"
which-pm-runs "^1.0.0"
precond@0.2:
version "0.2.3"
resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"
@ -11670,7 +11984,7 @@ process@0.11.10, process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
progress@^2.0.0:
progress@^2.0.0, progress@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
@ -12349,6 +12663,16 @@ rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0:
rc-resize-observer "^1.0.0"
rc-util "^5.0.7"
rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
dependencies:
deep-extend "^0.6.0"
ini "~1.3.0"
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-chartjs-2@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-3.0.4.tgz#88e14c6b4d577941c736f0657b071c8273916652"
@ -12556,7 +12880,7 @@ read@1, read@~1.0.1:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.5.0, readable-stream@^3.6.0:
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -13341,6 +13665,15 @@ simple-get@^2.7.0:
once "^1.3.1"
simple-concat "^1.0.0"
simple-get@^3.0.3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
dependencies:
decompress-response "^4.2.0"
once "^1.3.1"
simple-concat "^1.0.0"
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@ -13788,6 +14121,13 @@ stream-http@^2.7.2:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
stream-meter@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d"
integrity sha1-Uq+Vql6nYKJJFxZwTb/5D3Ov3R0=
dependencies:
readable-stream "^2.1.4"
stream-parser@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773"
@ -14011,6 +14351,11 @@ strip-json-comments@^3.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
strip-outer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631"
@ -14130,6 +14475,27 @@ tape@^4.6.3:
string.prototype.trim "~1.2.1"
through "~2.3.8"
tar-fs@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.1.4"
tar-stream@^2.1.4:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
version "4.4.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
@ -14441,6 +14807,11 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
tslib@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
tslib@^1.10.0, tslib@^1.7.1, tslib@^1.8.1, tslib@^1.9.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@ -14577,6 +14948,11 @@ typescript@^4.1.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
typescript@^4.3.5:
version "4.3.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
uglify-js@^3.1.4:
version "3.13.8"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.8.tgz#7c2f9f2553f611f3ff592bdc19c6fb208dc60afb"
@ -14653,6 +15029,11 @@ universalify@^0.1.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@ -15606,6 +15987,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yaeti@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
@ -15649,6 +16035,11 @@ yargs-parser@^15.0.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-parser@^20.2.3:
version "20.2.7"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
@ -15687,6 +16078,19 @@ yargs@^14.2.2:
y18n "^4.0.0"
yargs-parser "^15.0.1"
yargs@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"

12
rust/Anchor.toml Normal file
View File

@ -0,0 +1,12 @@
[registry]
url = "https://anchor.projectserum.com"
[provider]
cluster = "localnet"
wallet = "/Users/jprince/.config/solana/id.json"
[scripts]
test = "mocha -t 1000000 tests/"
[workspace]
members = ["nft-candy-machine"]

558
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
[workspace]
members = [
"nft-candy-machine/",
"auction/program",
"metaplex/program",
"token-vault/program",

View File

@ -15,8 +15,8 @@ clap = "2.33.3"
rand = "*"
solana-clap-utils = "1.6"
solana-cli-config = "1.6"
solana-client = "1.7.6"
solana-program = "1.7.6"
solana-sdk = "1.7.6"
solana-client = "1.7.8"
solana-program = "1.7.8"
solana-sdk = "1.7.8"
spl-auction = { path = "../program", features = [ "no-entrypoint" ] }
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }

View File

@ -17,13 +17,13 @@ borsh = "0.9.1"
num-derive = "0.3"
num-traits = "0.2"
arrayref = "0.3.6"
solana-program = "1.7.6"
solana-program = "1.7.8"
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
thiserror = "1.0"
[dev-dependencies]
solana-program-test = "1.7.6"
solana-sdk = "1.7.6"
solana-program-test = "1.7.8"
solana-sdk = "1.7.8"
[lib]
crate-type = ["cdylib", "lib"]

View File

@ -17,7 +17,7 @@ spl-auction = { path = "../../auction/program", features = [ "no-entrypoint" ] }
num-derive = "0.3"
num-traits = "0.2"
arrayref = "0.3.6"
solana-program = "1.7.6"
solana-program = "1.7.8"
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
spl-token-vault = { path = "../../token-vault/program", features = [ "no-entrypoint" ] }
spl-token-metadata = { path = "../../token-metadata/program", features = [ "no-entrypoint" ] }

View File

@ -9,9 +9,9 @@ edition = "2018"
publish = false
[dependencies]
solana-client = "1.7.6"
solana-program = "1.7.6"
solana-sdk = "1.7.6"
solana-client = "1.7.8"
solana-program = "1.7.8"
solana-sdk = "1.7.8"
bincode = "1.3.2"
arrayref = "0.3.6"
borsh = "0.9.1"

13
rust/migrations/deploy.js Normal file
View File

@ -0,0 +1,13 @@
// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Add your deploy script here.
}

View File

@ -0,0 +1,22 @@
[package]
name = "nft-candy-machine"
version = "0.1.0"
description = "Created with Anchor"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "nft_candy_machine"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = "0.13.2"
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

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,615 @@
pub mod utils;
use {
crate::utils::{assert_initialized, assert_owned_by, spl_token_transfer, TokenTransferParams},
anchor_lang::{
prelude::*, solana_program::system_program, AnchorDeserialize, AnchorSerialize,
Discriminator, Key,
},
arrayref::array_ref,
spl_token::state::{Account, Mint},
spl_token_metadata::{
instruction::{create_master_edition, create_metadata_accounts, update_metadata_accounts},
state::{
MAX_CREATOR_LEN, MAX_CREATOR_LIMIT, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH, MAX_URI_LENGTH,
},
},
std::cell::Ref,
};
const PREFIX: &str = "candy_machine";
#[program]
pub mod nft_candy_machine {
use anchor_lang::solana_program::{
program::{invoke, invoke_signed},
system_instruction,
};
use super::*;
pub fn mint_nft<'info>(ctx: Context<'_, '_, '_, 'info, MintNFT<'info>>) -> ProgramResult {
let candy_machine = &mut ctx.accounts.candy_machine;
let config = &ctx.accounts.config;
let clock = &ctx.accounts.clock;
match candy_machine.data.go_live_date {
None => {
if *ctx.accounts.payer.key != candy_machine.authority {
return Err(ErrorCode::CandyMachineNotLiveYet.into());
}
}
Some(val) => {
if clock.unix_timestamp < val {
if *ctx.accounts.payer.key != candy_machine.authority {
return Err(ErrorCode::CandyMachineNotLiveYet.into());
}
}
}
}
if candy_machine.items_redeemed >= candy_machine.data.items_available {
return Err(ErrorCode::CandyMachineEmpty.into());
}
if let Some(mint) = candy_machine.token_mint {
let token_account_info = &ctx.remaining_accounts[0];
let transfer_authority_info = &ctx.remaining_accounts[1];
let token_account: Account = assert_initialized(&token_account_info)?;
assert_owned_by(&token_account_info, &spl_token::id())?;
if token_account.mint != mint {
return Err(ErrorCode::MintMismatch.into());
}
if token_account.amount < candy_machine.data.price {
return Err(ErrorCode::NotEnoughTokens.into());
}
spl_token_transfer(TokenTransferParams {
source: token_account_info.clone(),
destination: ctx.accounts.wallet.clone(),
authority: transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: ctx.accounts.token_program.clone(),
amount: candy_machine.data.price,
})?;
} else {
if ctx.accounts.payer.lamports() < candy_machine.data.price {
return Err(ErrorCode::NotEnoughSOL.into());
}
invoke(
&system_instruction::transfer(
&ctx.accounts.payer.key,
ctx.accounts.wallet.key,
candy_machine.data.price,
),
&[
ctx.accounts.payer.clone(),
ctx.accounts.wallet.clone(),
ctx.accounts.system_program.clone(),
],
)?;
}
let config_line = get_config_line(
&config.to_account_info(),
candy_machine.items_redeemed as usize,
)?;
candy_machine.items_redeemed = candy_machine
.items_redeemed
.checked_add(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
let config_key = config.key();
let authority_seeds = [
PREFIX.as_bytes(),
config_key.as_ref(),
candy_machine.data.uuid.as_bytes(),
&[candy_machine.bump],
];
let mut creators: Vec<spl_token_metadata::state::Creator> =
vec![spl_token_metadata::state::Creator {
address: candy_machine.key(),
verified: true,
share: 0,
}];
for c in &config.data.creators {
creators.push(spl_token_metadata::state::Creator {
address: c.address,
verified: false,
share: c.share,
});
}
let metadata_infos = vec![
ctx.accounts.metadata.clone(),
ctx.accounts.mint.clone(),
ctx.accounts.mint_authority.clone(),
ctx.accounts.payer.clone(),
ctx.accounts.token_metadata_program.clone(),
ctx.accounts.token_program.clone(),
ctx.accounts.system_program.clone(),
ctx.accounts.rent.to_account_info().clone(),
candy_machine.to_account_info().clone(),
];
let master_edition_infos = vec![
ctx.accounts.master_edition.clone(),
ctx.accounts.mint.clone(),
ctx.accounts.mint_authority.clone(),
ctx.accounts.payer.clone(),
ctx.accounts.metadata.clone(),
ctx.accounts.token_metadata_program.clone(),
ctx.accounts.token_program.clone(),
ctx.accounts.system_program.clone(),
ctx.accounts.rent.to_account_info().clone(),
candy_machine.to_account_info().clone(),
];
invoke_signed(
&create_metadata_accounts(
*ctx.accounts.token_metadata_program.key,
*ctx.accounts.metadata.key,
*ctx.accounts.mint.key,
*ctx.accounts.mint_authority.key,
*ctx.accounts.payer.key,
candy_machine.key(),
config_line.name,
config.data.symbol.clone(),
config_line.uri,
Some(creators),
config.data.seller_fee_basis_points,
false,
config.data.is_mutable,
),
metadata_infos.as_slice(),
&[&authority_seeds],
)?;
invoke_signed(
&create_master_edition(
*ctx.accounts.token_metadata_program.key,
*ctx.accounts.master_edition.key,
*ctx.accounts.mint.key,
candy_machine.key(),
*ctx.accounts.mint_authority.key,
*ctx.accounts.metadata.key,
*ctx.accounts.payer.key,
Some(config.data.max_supply),
),
master_edition_infos.as_slice(),
&[&authority_seeds],
)?;
let mut new_update_authority = Some(candy_machine.authority);
if !ctx.accounts.config.data.retain_authority {
new_update_authority = Some(ctx.accounts.update_authority.key());
}
invoke_signed(
&update_metadata_accounts(
*ctx.accounts.token_metadata_program.key,
*ctx.accounts.metadata.key,
candy_machine.key(),
new_update_authority,
None,
Some(true),
),
&[
ctx.accounts.token_metadata_program.clone(),
ctx.accounts.metadata.clone(),
candy_machine.to_account_info().clone(),
],
&[&authority_seeds],
)?;
Ok(())
}
pub fn update_candy_machine(
ctx: Context<UpdateCandyMachine>,
price: Option<u64>,
go_live_date: Option<i64>,
) -> ProgramResult {
let candy_machine = &mut ctx.accounts.candy_machine;
if let Some(p) = price {
candy_machine.data.price = p;
}
if let Some(go_l) = go_live_date {
msg!("Go live date changed to {}", go_l);
candy_machine.data.go_live_date = Some(go_l)
}
Ok(())
}
pub fn initialize_config(ctx: Context<InitializeConfig>, data: ConfigData) -> ProgramResult {
let config_info = &mut ctx.accounts.config;
if data.uuid.len() != 6 {
return Err(ErrorCode::UuidMustBeExactly6Length.into());
}
let mut config = Config {
data,
authority: *ctx.accounts.authority.key,
};
let mut array_of_zeroes = vec![];
while array_of_zeroes.len() < MAX_SYMBOL_LENGTH - config.data.symbol.len() {
array_of_zeroes.push(0u8);
}
let new_symbol =
config.data.symbol.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
config.data.symbol = new_symbol;
// - 1 because we are going to be a creator
if config.data.creators.len() > MAX_CREATOR_LIMIT - 1 {
return Err(ErrorCode::TooManyCreators.into());
}
let mut new_data = Config::discriminator().try_to_vec().unwrap();
new_data.append(&mut config.try_to_vec().unwrap());
let mut data = config_info.data.borrow_mut();
// god forgive me couldnt think of better way to deal with this
for i in 0..new_data.len() {
data[i] = new_data[i];
}
let vec_start =
CONFIG_ARRAY_START + 4 + (config.data.max_number_of_lines as usize) * CONFIG_LINE_SIZE;
let as_bytes = (config
.data
.max_number_of_lines
.checked_div(8)
.ok_or(ErrorCode::NumericalOverflowError)? as u32)
.to_le_bytes();
for i in 0..4 {
data[vec_start + i] = as_bytes[i]
}
Ok(())
}
pub fn add_config_lines(
ctx: Context<AddConfigLines>,
index: u32,
config_lines: Vec<ConfigLine>,
) -> ProgramResult {
let config = &mut ctx.accounts.config;
let account = config.to_account_info();
let current_count = get_config_count(&account.data.borrow())?;
let mut data = account.data.borrow_mut();
let mut fixed_config_lines = vec![];
if index > config.data.max_number_of_lines - 1 {
return Err(ErrorCode::IndexGreaterThanLength.into());
}
for line in &config_lines {
let mut array_of_zeroes = vec![];
while array_of_zeroes.len() < MAX_NAME_LENGTH - line.name.len() {
array_of_zeroes.push(0u8);
}
let name = line.name.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
let mut array_of_zeroes = vec![];
while array_of_zeroes.len() < MAX_URI_LENGTH - line.uri.len() {
array_of_zeroes.push(0u8);
}
let uri = line.uri.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
fixed_config_lines.push(ConfigLine { name, uri })
}
let as_vec = fixed_config_lines.try_to_vec()?;
// remove unneeded u32 because we're just gonna edit the u32 at the front
let serialized: &[u8] = &as_vec.as_slice()[4..];
let position = CONFIG_ARRAY_START + 4 + (index as usize) * CONFIG_LINE_SIZE;
let array_slice: &mut [u8] =
&mut data[position..position + fixed_config_lines.len() * CONFIG_LINE_SIZE];
array_slice.copy_from_slice(serialized);
let bit_mask_vec_start = CONFIG_ARRAY_START
+ 4
+ (config.data.max_number_of_lines as usize) * CONFIG_LINE_SIZE
+ 4;
let mut new_count = current_count;
for i in 0..fixed_config_lines.len() {
let position = (index as usize)
.checked_add(i)
.ok_or(ErrorCode::NumericalOverflowError)?;
let my_position_in_vec = bit_mask_vec_start
+ position
.checked_div(8)
.ok_or(ErrorCode::NumericalOverflowError)?;
let position_from_right = 7 - position
.checked_rem(8)
.ok_or(ErrorCode::NumericalOverflowError)?;
let mask = u8::pow(2, position_from_right as u32);
let old_value_in_vec = data[my_position_in_vec];
data[my_position_in_vec] = data[my_position_in_vec] | mask;
msg!(
"My position in vec is {} my mask is going to be {}, the old value is {}",
position,
mask,
old_value_in_vec
);
msg!(
"My new value is {} and my position from right is {}",
data[my_position_in_vec],
position_from_right
);
if old_value_in_vec != data[my_position_in_vec] {
new_count = new_count
.checked_add(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
}
}
// plug in new count.
data[CONFIG_ARRAY_START..CONFIG_ARRAY_START + 4]
.copy_from_slice(&(new_count as u32).to_le_bytes());
Ok(())
}
pub fn initialize_candy_machine(
ctx: Context<InitializeCandyMachine>,
bump: u8,
data: CandyMachineData,
) -> ProgramResult {
let candy_machine = &mut ctx.accounts.candy_machine;
if data.uuid.len() != 6 {
return Err(ErrorCode::UuidMustBeExactly6Length.into());
}
candy_machine.data = data;
candy_machine.wallet = *ctx.accounts.wallet.key;
candy_machine.authority = *ctx.accounts.authority.key;
candy_machine.config = ctx.accounts.config.key();
candy_machine.bump = bump;
if ctx.remaining_accounts.len() > 0 {
let token_mint_info = &ctx.remaining_accounts[0];
let _token_mint: Mint = assert_initialized(&token_mint_info)?;
let token_account: Account = assert_initialized(&ctx.accounts.wallet)?;
assert_owned_by(&token_mint_info, &spl_token::id())?;
assert_owned_by(&ctx.accounts.wallet, &spl_token::id())?;
if token_account.mint != *token_mint_info.key {
return Err(ErrorCode::MintMismatch.into());
}
candy_machine.token_mint = Some(*token_mint_info.key);
}
if get_config_count(&ctx.accounts.config.to_account_info().data.borrow())?
!= candy_machine.data.items_available as usize
{
return Err(ErrorCode::ConfigLineMismatch.into());
}
let _config_line = match get_config_line(&ctx.accounts.config.to_account_info(), 0) {
Ok(val) => val,
Err(_) => return Err(ErrorCode::ConfigMustHaveAtleastOneEntry.into()),
};
Ok(())
}
}
#[derive(Accounts)]
#[instruction(bump: u8, data: CandyMachineData)]
pub struct InitializeCandyMachine<'info> {
#[account(init, seeds=[PREFIX.as_bytes(), config.key().as_ref(), data.uuid.as_bytes()], payer=payer, bump=bump, space=8+32+32+33+32+64+64+64+200)]
candy_machine: ProgramAccount<'info, CandyMachine>,
#[account(constraint= wallet.owner == &spl_token::id() || (wallet.data_is_empty() && wallet.lamports() > 0) )]
wallet: AccountInfo<'info>,
#[account(has_one=authority)]
config: ProgramAccount<'info, Config>,
#[account(signer, constraint= authority.data_is_empty() && authority.lamports() > 0)]
authority: AccountInfo<'info>,
#[account(mut, signer)]
payer: AccountInfo<'info>,
#[account(address = system_program::ID)]
system_program: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
}
#[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))]
config: AccountInfo<'info>,
#[account(constraint= authority.data_is_empty() && authority.lamports() > 0 )]
authority: AccountInfo<'info>,
#[account(mut, signer)]
payer: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct AddConfigLines<'info> {
#[account(mut, has_one = authority)]
config: ProgramAccount<'info, Config>,
#[account(signer)]
authority: AccountInfo<'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]])]
candy_machine: ProgramAccount<'info, CandyMachine>,
#[account(mut, signer)]
payer: AccountInfo<'info>,
#[account(mut)]
wallet: AccountInfo<'info>,
// With the following accounts we aren't using anchor macros because they are CPI'd
// through to token-metadata which will do all the validations we need on them.
#[account(mut)]
metadata: AccountInfo<'info>,
#[account(mut)]
mint: AccountInfo<'info>,
#[account(signer)]
mint_authority: AccountInfo<'info>,
#[account(signer)]
update_authority: AccountInfo<'info>,
#[account(mut)]
master_edition: AccountInfo<'info>,
#[account(address = spl_token_metadata::id())]
token_metadata_program: AccountInfo<'info>,
#[account(address = spl_token::id())]
token_program: AccountInfo<'info>,
#[account(address = system_program::ID)]
system_program: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
clock: Sysvar<'info, Clock>,
}
#[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]])]
candy_machine: ProgramAccount<'info, CandyMachine>,
#[account(signer)]
authority: AccountInfo<'info>,
}
#[account]
#[derive(Default)]
pub struct CandyMachine {
pub authority: Pubkey,
pub wallet: Pubkey,
pub token_mint: Option<Pubkey>,
pub config: Pubkey,
pub data: CandyMachineData,
pub items_redeemed: u64,
pub bump: u8,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
pub struct CandyMachineData {
pub uuid: String,
pub price: u64,
pub items_available: u64,
pub go_live_date: Option<i64>,
}
pub const CONFIG_ARRAY_START: usize = 32 + // authority
4 + 6 + // uuid + u32 len
4 + MAX_SYMBOL_LENGTH + // u32 len + symbol
2 + // seller fee basis points
1 + 4 + MAX_CREATOR_LIMIT*MAX_CREATOR_LEN + // optional + u32 len + actual vec
8 + //max supply
1 + // is mutable
1 + // retain authority
4; // max number of lines;
#[account]
#[derive(Default)]
pub struct Config {
pub authority: Pubkey,
pub data: ConfigData,
// there's a borsh vec u32 denoting how many actual lines of data there are currently (eventually equals max number of lines)
// There is actually lines and lines of data after this but we explicitly never want them deserialized.
// here there is a borsh vec u32 indicating number of bytes in bitmask array.
// here there is a number of bytes equal to ceil(max_number_of_lines/8) and it is a bit mask used to figure out when to increment borsh vec u32
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
pub struct ConfigData {
pub uuid: String,
/// The symbol for the asset
pub symbol: String,
/// Royalty basis points that goes to creators in secondary sales (0-10000)
pub seller_fee_basis_points: u16,
pub creators: Vec<Creator>,
pub max_supply: u64,
pub is_mutable: bool,
pub retain_authority: bool,
pub max_number_of_lines: u32,
}
pub fn get_config_count(data: &Ref<&mut [u8]>) -> core::result::Result<usize, ProgramError> {
return Ok(u32::from_le_bytes(*array_ref![data, CONFIG_ARRAY_START, 4]) as usize);
}
pub fn get_config_line(
a: &AccountInfo,
index: usize,
) -> core::result::Result<ConfigLine, ProgramError> {
let arr = a.data.borrow();
let total = get_config_count(&arr)?;
if index > total {
return Err(ErrorCode::IndexGreaterThanLength.into());
}
let data_array = &arr[CONFIG_ARRAY_START + 4 + index * (CONFIG_LINE_SIZE)
..CONFIG_ARRAY_START + 4 + (index + 1) * (CONFIG_LINE_SIZE)];
let config_line: ConfigLine = ConfigLine::try_from_slice(data_array)?;
Ok(config_line)
}
pub const CONFIG_LINE_SIZE: usize = 4 + MAX_NAME_LENGTH + 4 + MAX_URI_LENGTH;
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
pub struct ConfigLine {
/// The name of the asset
pub name: String,
/// URI pointing to JSON representing the asset
pub uri: String,
}
// Unfortunate duplication of token metadata so that IDL picks it up.
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct Creator {
pub address: Pubkey,
pub verified: bool,
// In percentages, NOT basis points ;) Watch out!
pub share: u8,
}
#[error]
pub enum ErrorCode {
#[msg("Account does not have correct owner!")]
IncorrectOwner,
#[msg("Account is not initialized!")]
Uninitialized,
#[msg("Mint Mismatch!")]
MintMismatch,
#[msg("Index greater than length!")]
IndexGreaterThanLength,
#[msg("Config must have atleast one entry!")]
ConfigMustHaveAtleastOneEntry,
#[msg("Numerical overflow error!")]
NumericalOverflowError,
#[msg("Can only provide up to 4 creators to candy machine (because candy machine is one)!")]
TooManyCreators,
#[msg("Uuid must be exactly of 6 length")]
UuidMustBeExactly6Length,
#[msg("Not enough tokens to pay for this minting")]
NotEnoughTokens,
#[msg("Not enough SOL to pay for this minting")]
NotEnoughSOL,
#[msg("Token transfer failed")]
TokenTransferFailed,
#[msg("Candy machine is empty!")]
CandyMachineEmpty,
#[msg("Candy machine is not live yet!")]
CandyMachineNotLiveYet,
#[msg("Number of config lines must match items available")]
ConfigLineMismatch,
}

View File

@ -0,0 +1,71 @@
use {
crate::ErrorCode,
anchor_lang::{
prelude::{AccountInfo, ProgramError, ProgramResult, Pubkey},
solana_program::{
program::invoke_signed,
program_pack::{IsInitialized, Pack},
},
},
};
pub fn assert_initialized<T: Pack + IsInitialized>(
account_info: &AccountInfo,
) -> Result<T, ProgramError> {
let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
if !account.is_initialized() {
Err(ErrorCode::Uninitialized.into())
} else {
Ok(account)
}
}
pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult {
if account.owner != owner {
Err(ErrorCode::IncorrectOwner.into())
} else {
Ok(())
}
}
///TokenTransferParams
pub struct TokenTransferParams<'a: 'b, 'b> {
/// source
pub source: AccountInfo<'a>,
/// destination
pub destination: AccountInfo<'a>,
/// amount
pub amount: u64,
/// authority
pub authority: AccountInfo<'a>,
/// authority_signer_seeds
pub authority_signer_seeds: &'b [&'b [u8]],
/// token_program
pub token_program: AccountInfo<'a>,
}
#[inline(always)]
pub fn spl_token_transfer(params: TokenTransferParams<'_, '_>) -> ProgramResult {
let TokenTransferParams {
source,
destination,
authority,
token_program,
amount,
authority_signer_seeds,
} = params;
let result = invoke_signed(
&spl_token::instruction::transfer(
token_program.key,
source.key,
destination.key,
authority.key,
&[],
amount,
)?,
&[source, destination, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| ErrorCode::TokenTransferFailed.into())
}

26
rust/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "@metaplex/metaplex-programs",
"version": "1.0.0",
"private": true,
"description": "Metaplex Program Tests",
"license": "MIT",
"type": "module",
"author": "Metaplex Contributors",
"devDependencies": {
"@types/mocha": "^9.0.0",
"@project-serum/anchor": "^0.13.2",
"mocha": "^9.0.3",
"ts-mocha": "^8.0.0",
"ts-node": "^10.2.1",
"typescript": "^4.3.5",
"@solana/web3.js": "^1.21.0"
},
"scripts": {
"idl": "node test/idlToTs",
"test": "env MY_WALLET=$HOME/.config/solana/id.json ts-mocha -p ./tsconfig.json -t 1000000 test/*.ts"
},
"dependencies": {
"@project-serum/common": "^0.0.1-beta.3",
"quicktype-core": "^6.0.70"
}
}

95
rust/test/idlToTs.js Normal file
View File

@ -0,0 +1,95 @@
import { watch, readdir, readFile, writeFile } from "fs/promises";
import camelcase from "camelcase";
const idl = "./target/idl/";
function enumVariantToTs(variant) {
return `${variant.name}: { ${variant.name.toLowerCase()}: {} }`;
}
function enumToTs(struct) {
const enumVariants = struct.type.variants;
if (enumVariants) {
return `export type ${capitalizeFirstLetter(
struct.name
)} = Record<string, Record<string, any>>
export const ${capitalizeFirstLetter(struct.name)} = {
${enumVariants.map(enumVariantToTs).join(",\n ")}
}
`;
}
}
function allEnumsToTs(idlDef) {
return `
${idlDef.types ? idlDef.types.map(enumToTs).filter(Boolean).join("\n\n") : ""}
${
idlDef.accounts
? idlDef.accounts.map(enumToTs).filter(Boolean).join("\n\n")
: ""
}
`;
}
function accountToTs(idlName, account) {
return `export type ${capitalizeFirstLetter(
account.name
)} = IdlAccounts<${idlName}>["${account.name}"]`;
}
function allAccountsToTs(idlName, idlDef) {
return `
${idlDef.accounts
.map((a) => accountToTs(idlName, a))
.filter(Boolean)
.join("\n\n")}
`;
}
function capitalizeFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}
(async () => {
const files = await readdir(idl);
await Promise.all(
files.map(async (filename) => {
try {
const path = `${idl}${filename}`;
const watcher = watch(path);
async function generate() {
const rawdata = await readFile(path);
console.log(`Change in ${path}`);
const name = filename.replace(".json", "").replace(/\_/g, "-");
let idlJson = JSON.parse(rawdata.toString());
for (let account of idlJson.accounts) {
account.name = camelcase(account.name);
}
const idlName = `${capitalizeFirstLetter(camelcase(name))}IDL`;
const fileContents = `export type ${idlName} = ${JSON.stringify(
idlJson
)};
import { IdlAccounts } from '@project-serum/anchor';
${allEnumsToTs(idlJson)}
${allAccountsToTs(idlName, idlJson)}
`;
writeFile(`./test/${name}-types.ts`, fileContents);
}
await generate();
for await (const event of watcher) {
await generate();
}
} catch (err) {
if (err.name === "AbortError") {
console.log("Aborted");
console.log(err);
return;
} else {
throw err;
}
}
})
);
})();

View File

@ -0,0 +1,12 @@
export type NftCandyMachineIDL = {"version":"0.0.0","name":"nft_candy_machine","instructions":[{"name":"mintNft","accounts":[{"name":"config","isMut":false,"isSigner":false},{"name":"candyMachine","isMut":true,"isSigner":false},{"name":"payer","isMut":true,"isSigner":true},{"name":"wallet","isMut":true,"isSigner":false},{"name":"metadata","isMut":true,"isSigner":false},{"name":"mint","isMut":true,"isSigner":false},{"name":"mintAuthority","isMut":false,"isSigner":true},{"name":"updateAuthority","isMut":false,"isSigner":true},{"name":"masterEdition","isMut":true,"isSigner":false},{"name":"tokenMetadataProgram","isMut":false,"isSigner":false},{"name":"tokenProgram","isMut":false,"isSigner":false},{"name":"systemProgram","isMut":false,"isSigner":false},{"name":"rent","isMut":false,"isSigner":false},{"name":"clock","isMut":false,"isSigner":false}],"args":[]},{"name":"updateCandyMachine","accounts":[{"name":"candyMachine","isMut":true,"isSigner":false},{"name":"authority","isMut":false,"isSigner":true}],"args":[{"name":"price","type":{"option":"u64"}},{"name":"goLiveDate","type":{"option":"i64"}}]},{"name":"initializeConfig","accounts":[{"name":"config","isMut":true,"isSigner":false},{"name":"authority","isMut":false,"isSigner":false},{"name":"payer","isMut":true,"isSigner":true},{"name":"rent","isMut":false,"isSigner":false}],"args":[{"name":"data","type":{"defined":"ConfigData"}}]},{"name":"addConfigLines","accounts":[{"name":"config","isMut":true,"isSigner":false},{"name":"authority","isMut":false,"isSigner":true}],"args":[{"name":"index","type":"u32"},{"name":"configLines","type":{"vec":{"defined":"ConfigLine"}}}]},{"name":"initializeCandyMachine","accounts":[{"name":"candyMachine","isMut":true,"isSigner":false},{"name":"wallet","isMut":false,"isSigner":false},{"name":"config","isMut":false,"isSigner":false},{"name":"authority","isMut":false,"isSigner":true},{"name":"payer","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false},{"name":"rent","isMut":false,"isSigner":false}],"args":[{"name":"bump","type":"u8"},{"name":"data","type":{"defined":"CandyMachineData"}}]}],"accounts":[{"name":"candyMachine","type":{"kind":"struct","fields":[{"name":"authority","type":"publicKey"},{"name":"wallet","type":"publicKey"},{"name":"tokenMint","type":{"option":"publicKey"}},{"name":"config","type":"publicKey"},{"name":"data","type":{"defined":"CandyMachineData"}},{"name":"itemsRedeemed","type":"u64"},{"name":"bump","type":"u8"}]}},{"name":"config","type":{"kind":"struct","fields":[{"name":"authority","type":"publicKey"},{"name":"data","type":{"defined":"ConfigData"}}]}}],"types":[{"name":"CandyMachineData","type":{"kind":"struct","fields":[{"name":"uuid","type":"string"},{"name":"price","type":"u64"},{"name":"itemsAvailable","type":"u64"},{"name":"goLiveDate","type":{"option":"i64"}}]}},{"name":"ConfigData","type":{"kind":"struct","fields":[{"name":"uuid","type":"string"},{"name":"symbol","type":"string"},{"name":"sellerFeeBasisPoints","type":"u16"},{"name":"creators","type":{"vec":{"defined":"Creator"}}},{"name":"maxSupply","type":"u64"},{"name":"isMutable","type":"bool"},{"name":"retainAuthority","type":"bool"},{"name":"maxNumberOfLines","type":"u32"}]}},{"name":"ConfigLine","type":{"kind":"struct","fields":[{"name":"name","type":"string"},{"name":"uri","type":"string"}]}},{"name":"Creator","type":{"kind":"struct","fields":[{"name":"address","type":"publicKey"},{"name":"verified","type":"bool"},{"name":"share","type":"u8"}]}}],"errors":[{"code":300,"name":"IncorrectOwner","msg":"Account does not have correct owner!"},{"code":301,"name":"Uninitialized","msg":"Account is not initialized!"},{"code":302,"name":"MintMismatch","msg":"Mint Mismatch!"},{"code":303,"name":"IndexGreaterThanLength","msg":"Index greater than length!"},{"code":304,"name":"ConfigMustHaveAtleastOneEntry","msg":"Config must have atleast one entry!"},{"code":305,"name":"NumericalOverflowError","msg":"Numerical overflow error!"},{"code":306,"name":"TooManyCreators","msg":"Can only provide up to 4 creators to candy machine (because candy machine is one)!"},{"code":307,"name":"UuidMustBeExactly6Length","msg":"Uuid must be exactly of 6 length"},{"code":308,"name":"NotEnoughTokens","msg":"Not enough tokens to pay for this minting"},{"code":309,"name":"NotEnoughSOL","msg":"Not enough SOL to pay for this minting"},{"code":310,"name":"TokenTransferFailed","msg":"Token transfer failed"},{"code":311,"name":"CandyMachineEmpty","msg":"Candy machine is empty!"},{"code":312,"name":"CandyMachineNotLiveYet","msg":"Candy machine is not live yet!"},{"code":313,"name":"ConfigLineMismatch","msg":"Number of config lines must match items available"}]};
import { IdlAccounts } from '@project-serum/anchor';
export type CandyMachine = IdlAccounts<NftCandyMachineIDL>["candyMachine"]
export type Config = IdlAccounts<NftCandyMachineIDL>["config"]

View File

@ -0,0 +1,923 @@
import * as anchor from "@project-serum/anchor";
import assert from "assert";
import { AccountLayout, MintLayout, Token } from "@solana/spl-token";
import {
PublicKey,
SystemProgram,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from "@solana/web3.js";
import { CandyMachine, Config } from "./nft-candy-machine-types";
const TOKEN_PROGRAM_ID = new PublicKey(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
);
const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
);
const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);
function fromUTF8Array(data: number[]) {
// array of bytes
let str = "",
i;
for (i = 0; i < data.length; i++) {
const value = data[i];
if (value < 0x80) {
str += String.fromCharCode(value);
} else if (value > 0xbf && value < 0xe0) {
str += String.fromCharCode(((value & 0x1f) << 6) | (data[i + 1] & 0x3f));
i += 1;
} else if (value > 0xdf && value < 0xf0) {
str += String.fromCharCode(
((value & 0x0f) << 12) |
((data[i + 1] & 0x3f) << 6) |
(data[i + 2] & 0x3f)
);
i += 2;
} else {
// surrogate pair
const charCode =
(((value & 0x07) << 18) |
((data[i + 1] & 0x3f) << 12) |
((data[i + 2] & 0x3f) << 6) |
(data[i + 3] & 0x3f)) -
0x010000;
str += String.fromCharCode(
(charCode >> 10) | 0xd800,
(charCode & 0x03ff) | 0xdc00
);
i += 3;
}
}
return str;
}
export function createAssociatedTokenAccountInstruction(
associatedTokenAddress: PublicKey,
payer: PublicKey,
walletAddress: PublicKey,
splTokenMintAddress: PublicKey
) {
const keys = [
{
pubkey: payer,
isSigner: true,
isWritable: true,
},
{
pubkey: associatedTokenAddress,
isSigner: false,
isWritable: true,
},
{
pubkey: walletAddress,
isSigner: false,
isWritable: false,
},
{
pubkey: splTokenMintAddress,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{
pubkey: TOKEN_PROGRAM_ID,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
];
return new TransactionInstruction({
keys,
programId: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
data: Buffer.from([]),
});
}
const configArrayStart =
32 + // authority
4 +
6 + // uuid + u32 len
4 +
10 + // u32 len + symbol
2 + // seller fee basis points
1 +
4 +
5 * 34 + // optional + u32 len + actual vec
8 + //max supply
1 + //is mutable
1 + // retain authority
4; // max number of lines;
const configLineSize = 4 + 32 + 4 + 200;
const CANDY_MACHINE = "candy_machine";
describe("nft-candy-machine", function () {
// Configure the client to use the local cluster.
const idl = JSON.parse(
require("fs").readFileSync("./target/idl/nft_candy_machine.json", "utf8")
);
const myWallet = anchor.web3.Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(require("fs").readFileSync(process.env.MY_WALLET, "utf8"))
)
);
const connection = new anchor.web3.Connection(
"https://api.devnet.solana.com/",
"recent"
);
// Address of the deployed program.
const programId = new anchor.web3.PublicKey(
"cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ"
);
const walletWrapper = new anchor.Wallet(myWallet);
const provider = new anchor.Provider(connection, walletWrapper, {
preflightCommitment: "recent",
});
const program = new anchor.Program(idl, programId, provider);
const getCandyMachine = async (
config: anchor.web3.PublicKey,
uuid: string
) => {
return await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from(CANDY_MACHINE), config.toBuffer(), Buffer.from(uuid)],
programId
);
};
const getMetadata = async (
mint: anchor.web3.PublicKey
): Promise<anchor.web3.PublicKey> => {
return (
await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from("metadata"),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
],
TOKEN_METADATA_PROGRAM_ID
)
)[0];
};
const getMasterEdition = async (
mint: anchor.web3.PublicKey
): Promise<anchor.web3.PublicKey> => {
return (
await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from("metadata"),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
Buffer.from("edition"),
],
TOKEN_METADATA_PROGRAM_ID
)
)[0];
};
const createConfig = async function (
that,
retainAuthority: boolean,
size: number
): Promise<TransactionInstruction> {
that.authority = anchor.web3.Keypair.generate();
that.uuid = anchor.web3.Keypair.generate().publicKey.toBase58().slice(0, 6);
return await program.instruction.initializeConfig(
{
uuid: that.uuid,
maxNumberOfLines: new anchor.BN(size),
symbol: "SYMBOL",
sellerFeeBasisPoints: 500,
isMutable: true,
maxSupply: new anchor.BN(0),
retainAuthority,
creators: [
{ address: myWallet.publicKey, verified: false, share: 100 },
],
},
{
accounts: {
config: that.config.publicKey,
authority: that.authority.publicKey,
payer: myWallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [myWallet, that.config],
}
);
};
const addConfigLines = async function (
that,
size: number
): Promise<TransactionInstruction[]> {
const sample = {
uri: "www.aol.com",
isMutable: true,
};
const firstVec = [];
for (let i = 0; i < 5; i++) {
firstVec.push({ ...sample, name: `Sample ${i}` });
}
const tx1 = await program.instruction.addConfigLines(0, firstVec, {
accounts: {
config: that.config.publicKey,
authority: that.authority.publicKey,
},
signers: [that.authority, myWallet],
});
if (size != 5) {
const secondVec = [];
for (let i = 5; i < 10; i++) {
secondVec.push({ ...sample, name: `Sample ${i}` });
}
const tx2 = await program.instruction.addConfigLines(5, secondVec, {
accounts: {
config: that.config.publicKey,
authority: that.authority.publicKey,
},
signers: [that.authority, myWallet],
});
// Run tx2 twice to simulate an overwrite which might tip counter to overcount.
return [tx1, tx2, tx2];
} else return [tx1];
};
const getTokenWallet = async function (wallet: PublicKey, mint: PublicKey) {
return (
await PublicKey.findProgramAddress(
[wallet.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()],
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
)
)[0];
};
describe("sol only", function () {
beforeEach(async function () {
const config = await anchor.web3.Keypair.generate();
this.config = config;
const txInstr = await createConfig(this, false, 10);
const linesInstr = await addConfigLines(this, 10);
this.candyMachineUuid = anchor.web3.Keypair.generate()
.publicKey.toBase58()
.slice(0, 6);
const [candyMachine, bump] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
try {
const tx = await program.rpc.initializeCandyMachine(
bump,
{
uuid: this.candyMachineUuid,
price: new anchor.BN(1000000000),
itemsAvailable: new anchor.BN(10),
goLiveDate: null,
},
{
accounts: {
candyMachine,
wallet: myWallet.publicKey,
config: this.config.publicKey,
authority: this.authority.publicKey,
payer: myWallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [myWallet, this.authority, this.config],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: myWallet.publicKey,
newAccountPubkey: config.publicKey,
space: configArrayStart + 4 + 10 * configLineSize + 4 + 2,
lamports:
await provider.connection.getMinimumBalanceForRentExemption(
configArrayStart + 4 + 10 * configLineSize + 4 + 2
),
programId: programId,
}),
anchor.web3.SystemProgram.transfer({
fromPubkey: myWallet.publicKey,
toPubkey: this.authority.publicKey,
lamports: 5,
}),
txInstr,
...linesInstr,
],
}
);
} catch (e) {
console.log(e);
throw e;
}
});
it("has all ten lines", async function () {
const config = await connection.getAccountInfo(this.config.publicKey);
const amountOfConfigs = new anchor.BN(
config.data.slice(configArrayStart, configArrayStart + 4),
"le"
);
assert.equal(amountOfConfigs.toNumber(), 10);
for (let i = 0; i < amountOfConfigs.toNumber(); i++) {
const thisSlice = config.data.slice(
configArrayStart + 4 + configLineSize * i,
configArrayStart + 4 + configLineSize * (i + 1)
);
const name = fromUTF8Array([...thisSlice.slice(4, 36)]);
const uri = fromUTF8Array([...thisSlice.slice(40, 240)]);
assert.equal(name.replace(/\0/g, "").trim(), `Sample ${i}`);
assert.equal(uri.replace(/\0/g, "").trim(), "www.aol.com");
}
});
it("Is initialized!", async function () {
// Add your test here.
const [candyMachine, bump] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
const machine: CandyMachine = await program.account.candyMachine.fetch(
candyMachine
);
assert.equal(machine.data.uuid, this.candyMachineUuid);
assert.ok(machine.wallet.equals(myWallet.publicKey));
assert.ok(machine.config.equals(this.config.publicKey));
assert.ok(machine.authority.equals(this.authority.publicKey));
assert.equal(
machine.data.price.toNumber(),
new anchor.BN(1000000000).toNumber()
);
assert.equal(machine.bump, bump);
assert.equal(
machine.data.itemsAvailable.toNumber(),
new anchor.BN(10).toNumber()
);
assert.equal(machine.tokenMint, null);
});
it("mints 10x and then ends due to being out of candy", async function () {
for (let i = 0; i < 11; i++) {
const mint = anchor.web3.Keypair.generate();
const token = await getTokenWallet(
this.authority.publicKey,
mint.publicKey
);
const metadata = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
const [candyMachine, _] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
try {
const tx = await program.rpc.mintNft({
accounts: {
config: this.config.publicKey,
candyMachine: candyMachine,
payer: this.authority.publicKey,
wallet: myWallet.publicKey,
mint: mint.publicKey,
metadata,
masterEdition,
mintAuthority: this.authority.publicKey,
updateAuthority: this.authority.publicKey,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
signers: [mint, this.authority, myWallet],
instructions: [
// Give authority enough to pay off the cost of the nft!
// it'll be funnneled right back
anchor.web3.SystemProgram.transfer({
fromPubkey: myWallet.publicKey,
toPubkey: this.authority.publicKey,
lamports: 1000000000 + 10000000, // add minting fees in there
}),
anchor.web3.SystemProgram.createAccount({
fromPubkey: myWallet.publicKey,
newAccountPubkey: mint.publicKey,
space: MintLayout.span,
lamports:
await provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
0,
this.authority.publicKey,
this.authority.publicKey
),
createAssociatedTokenAccountInstruction(
token,
myWallet.publicKey,
this.authority.publicKey,
mint.publicKey
),
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
token,
this.authority.publicKey,
[],
1
),
],
});
} catch (e) {
if (i != 10) {
console.log("Failure at ", i, e);
throw e;
}
}
if (i != 10) {
const metadataAccount = await connection.getAccountInfo(metadata);
assert.ok(metadataAccount.data.length > 0);
const masterEditionAccount = await connection.getAccountInfo(
masterEdition
);
assert.ok(masterEditionAccount.data.length > 0);
}
}
});
it("mints with goLive date not as the authority over the candy machine", async function () {
// myWallet isnt authority, this.authority is, so shouldnt be able to mint until goLive set.
const mint = anchor.web3.Keypair.generate();
const token = await getTokenWallet(myWallet.publicKey, mint.publicKey);
const metadata = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
const [candyMachine, _] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
try {
const tx = await program.rpc.mintNft({
accounts: {
config: this.config.publicKey,
candyMachine,
payer: myWallet.publicKey,
wallet: myWallet.publicKey,
mint: mint.publicKey,
metadata,
masterEdition,
mintAuthority: myWallet.publicKey,
updateAuthority: myWallet.publicKey,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
signers: [mint, this.authority, myWallet],
instructions: [
program.instruction.updateCandyMachine(null, new anchor.BN(500), {
accounts: {
candyMachine,
authority: this.authority.publicKey,
},
}),
anchor.web3.SystemProgram.createAccount({
fromPubkey: myWallet.publicKey,
newAccountPubkey: mint.publicKey,
space: MintLayout.span,
lamports:
await provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
0,
myWallet.publicKey,
myWallet.publicKey
),
createAssociatedTokenAccountInstruction(
token,
myWallet.publicKey,
myWallet.publicKey,
mint.publicKey
),
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
token,
myWallet.publicKey,
[],
1
),
],
});
} catch (e) {
console.log(e);
throw e;
}
const metadataAccount = await connection.getAccountInfo(metadata);
assert.ok(metadataAccount.data.length > 0);
const metadataAuthority = metadataAccount.data.slice(1, 33).join("");
assert.equal(metadataAuthority, myWallet.publicKey.toBytes().join(""));
const masterEditionAccount = await connection.getAccountInfo(
masterEdition
);
assert.ok(masterEditionAccount.data.length > 0);
});
it("mints without goLive date", async function () {
const authorityLamports = await connection.getBalance(
this.authority.publicKey
);
const walletLamports = await connection.getBalance(myWallet.publicKey);
const mint = anchor.web3.Keypair.generate();
const token = await getTokenWallet(
this.authority.publicKey,
mint.publicKey
);
const metadata = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
const [candyMachine, _] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
try {
const tx = await program.rpc.mintNft({
accounts: {
config: this.config.publicKey,
candyMachine: candyMachine,
payer: this.authority.publicKey,
wallet: myWallet.publicKey,
mint: mint.publicKey,
metadata,
masterEdition,
mintAuthority: this.authority.publicKey,
updateAuthority: this.authority.publicKey,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
signers: [mint, this.authority, myWallet],
instructions: [
// Give authority enough to pay off the cost of the nft!
// it'll be funnneled right back
anchor.web3.SystemProgram.transfer({
fromPubkey: myWallet.publicKey,
toPubkey: this.authority.publicKey,
lamports: 1000000000 + 10000000, // add minting fees in there
}),
anchor.web3.SystemProgram.createAccount({
fromPubkey: myWallet.publicKey,
newAccountPubkey: mint.publicKey,
space: MintLayout.span,
lamports:
await provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
0,
this.authority.publicKey,
this.authority.publicKey
),
createAssociatedTokenAccountInstruction(
token,
myWallet.publicKey,
this.authority.publicKey,
mint.publicKey
),
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
token,
this.authority.publicKey,
[],
1
),
],
});
} catch (e) {
console.log(e);
throw e;
}
const metadataAccount = await connection.getAccountInfo(metadata);
assert.ok(metadataAccount.data.length > 0);
const masterEditionAccount = await connection.getAccountInfo(
masterEdition
);
assert.ok(masterEditionAccount.data.length > 0);
// since we transferred in the exact amount in advance from our own wallet,
// should be no net change in authority, and should be minor change in wallet
// since nft price paid back. Only real cost should be tx fees.
const newAuthorityLamports = await connection.getBalance(
this.authority.publicKey
);
const newWalletLamports = await connection.getBalance(myWallet.publicKey);
assert.ok(authorityLamports - newAuthorityLamports < 10000);
// less minting fees...
assert.ok(walletLamports - newWalletLamports < 15000000);
});
});
describe("token", function () {
beforeEach(async function () {
const config = await anchor.web3.Keypair.generate();
this.config = config;
const txInstr = await createConfig(this, true, 5);
const linesInstr = await addConfigLines(this, 5);
this.tokenMint = anchor.web3.Keypair.generate();
this.candyMachineUuid = anchor.web3.Keypair.generate()
.publicKey.toBase58()
.slice(0, 6);
const [candyMachine, bump] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
this.walletToken = await getTokenWallet(
myWallet.publicKey,
this.tokenMint.publicKey
);
try {
const tx = await program.rpc.initializeCandyMachine(
bump,
{
uuid: this.candyMachineUuid,
price: new anchor.BN(1),
itemsAvailable: new anchor.BN(5),
goLiveDate: null,
},
{
accounts: {
candyMachine,
wallet: this.walletToken,
config: this.config.publicKey,
authority: this.authority.publicKey,
payer: myWallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
remainingAccounts: [
{
pubkey: this.tokenMint.publicKey,
isWritable: false,
isSigner: true,
},
],
signers: [myWallet, this.tokenMint, this.authority, this.config],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: myWallet.publicKey,
newAccountPubkey: config.publicKey,
space: configArrayStart + 4 + 5 * configLineSize + 4 + 1,
lamports:
await provider.connection.getMinimumBalanceForRentExemption(
configArrayStart + 4 + 5 * configLineSize + 4 + 1
),
programId: programId,
}),
anchor.web3.SystemProgram.transfer({
fromPubkey: myWallet.publicKey,
toPubkey: this.authority.publicKey,
lamports: 5,
}),
anchor.web3.SystemProgram.createAccount({
fromPubkey: myWallet.publicKey,
newAccountPubkey: this.tokenMint.publicKey,
space: MintLayout.span,
lamports:
await provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
this.tokenMint.publicKey,
0,
myWallet.publicKey,
myWallet.publicKey
),
createAssociatedTokenAccountInstruction(
this.walletToken,
myWallet.publicKey,
myWallet.publicKey,
this.tokenMint.publicKey
),
txInstr,
...linesInstr,
],
}
);
} catch (e) {
console.log(e);
throw e;
}
});
it("Is initialized!", async function () {
// Add your test here.
const [candyMachine, bump] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
const machine: CandyMachine = await program.account.candyMachine.fetch(
candyMachine
);
assert.equal(machine.data.uuid, this.candyMachineUuid);
assert.ok(machine.wallet.equals(this.walletToken));
assert.ok(machine.config.equals(this.config.publicKey));
assert.ok(machine.authority.equals(this.authority.publicKey));
assert.equal(machine.data.price.toNumber(), new anchor.BN(1).toNumber());
assert.equal(machine.bump, bump);
assert.equal(
machine.data.itemsAvailable.toNumber(),
new anchor.BN(5).toNumber()
);
assert.ok(machine.tokenMint.equals(this.tokenMint.publicKey));
});
it("mints without goLive date", async function () {
const walletTokens = await connection.getTokenAccountBalance(
this.walletToken
);
const mint = anchor.web3.Keypair.generate();
const token = await getTokenWallet(
this.authority.publicKey,
mint.publicKey
);
const transferAuthority = anchor.web3.Keypair.generate();
const payingToken = await getTokenWallet(
this.authority.publicKey,
this.tokenMint.publicKey
);
const metadata = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
const [candyMachine, _] = await getCandyMachine(
this.config.publicKey,
this.candyMachineUuid
);
try {
const tx = await program.rpc.mintNft({
accounts: {
config: this.config.publicKey,
candyMachine: candyMachine,
payer: this.authority.publicKey,
wallet: this.walletToken,
mint: mint.publicKey,
metadata,
masterEdition,
mintAuthority: this.authority.publicKey,
updateAuthority: this.authority.publicKey,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
remainingAccounts: [
{
pubkey: payingToken,
isWritable: true,
isSigner: false,
},
{
pubkey: transferAuthority.publicKey,
isWritable: false,
isSigner: true,
},
],
signers: [mint, this.authority, myWallet, transferAuthority],
instructions: [
// Give authority enough to pay off the cost of the nft!
// it'll be funnneled right back
anchor.web3.SystemProgram.transfer({
fromPubkey: myWallet.publicKey,
toPubkey: this.authority.publicKey,
lamports: 10000000, // add minting fees in there
}),
anchor.web3.SystemProgram.createAccount({
fromPubkey: myWallet.publicKey,
newAccountPubkey: mint.publicKey,
space: MintLayout.span,
lamports:
await provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
0,
this.authority.publicKey,
this.authority.publicKey
),
createAssociatedTokenAccountInstruction(
token,
myWallet.publicKey,
this.authority.publicKey,
mint.publicKey
),
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
token,
this.authority.publicKey,
[],
1
),
// token account we use to pay
createAssociatedTokenAccountInstruction(
payingToken,
myWallet.publicKey,
this.authority.publicKey,
this.tokenMint.publicKey
),
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
this.tokenMint.publicKey,
payingToken,
myWallet.publicKey,
[],
1
),
Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
payingToken,
transferAuthority.publicKey,
this.authority.publicKey,
[],
1
),
],
});
} catch (e) {
console.log(e);
throw e;
}
const metadataAccount = await connection.getAccountInfo(metadata);
assert.ok(metadataAccount.data.length > 0);
const masterEditionAccount = await connection.getAccountInfo(
masterEdition
);
assert.ok(masterEditionAccount.data.length > 0);
const newWalletTokens = await connection.getTokenAccountBalance(
this.walletToken
);
assert.ok(
newWalletTokens.value.uiAmount - walletTokens.value.uiAmount == 1
);
const payingTokenBalance = await connection.getTokenAccountBalance(
payingToken
);
assert.equal(payingTokenBalance.value.uiAmount, 0);
});
});
});

View File

@ -16,7 +16,7 @@ test-bpf = []
num-derive = "0.3"
arrayref = "0.3.6"
num-traits = "0.2"
solana-program = "1.7.6"
solana-program = "1.7.8"
spl-token-vault = { path = "../../token-vault/program", features = [ "no-entrypoint" ] }
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
thiserror = "1.0"

View File

@ -268,7 +268,7 @@ pub fn create_metadata_accounts(
AccountMeta::new(metadata_account, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new_readonly(mint_authority, true),
AccountMeta::new_readonly(payer, true),
AccountMeta::new(payer, true),
AccountMeta::new_readonly(update_authority, update_authority_is_signer),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
@ -360,7 +360,7 @@ pub fn create_master_edition(
AccountMeta::new(mint, false),
AccountMeta::new_readonly(update_authority, true),
AccountMeta::new_readonly(mint_authority, true),
AccountMeta::new_readonly(payer, true),
AccountMeta::new(payer, true),
AccountMeta::new_readonly(metadata, false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
@ -434,6 +434,25 @@ pub fn mint_new_edition_from_master_edition_via_token(
}
}
/// Sign Metadata
#[allow(clippy::too_many_arguments)]
pub fn sign_metadata(
program_id: Pubkey,
metadata: Pubkey,
creator: Pubkey,
) -> Instruction {
Instruction {
program_id,
accounts: vec![
AccountMeta::new(metadata, false),
AccountMeta::new_readonly(creator, true),
],
data: MetadataInstruction::SignMetadata
.try_to_vec()
.unwrap(),
}
}
/// Converts a master edition v1 to v2
#[allow(clippy::too_many_arguments)]
pub fn convert_master_edition_v1_to_v2(

View File

@ -20,10 +20,9 @@ pub const MAX_SYMBOL_LENGTH: usize = 10;
pub const MAX_URI_LENGTH: usize = 200;
pub const MAX_METADATA_LEN: usize = 1
+ 32
+ 32
+ 4
pub const MAX_METADATA_LEN: usize = 1 + 32 + 32 + MAX_DATA_SIZE + 1 + 1 + 9 + 172;
pub const MAX_DATA_SIZE: usize = 4
+ MAX_NAME_LENGTH
+ 4
+ MAX_SYMBOL_LENGTH
@ -32,11 +31,7 @@ pub const MAX_METADATA_LEN: usize = 1
+ 2
+ 1
+ 4
+ MAX_CREATOR_LIMIT * MAX_CREATOR_LEN
+ 1
+ 1
+ 9
+ 172;
+ MAX_CREATOR_LIMIT * MAX_CREATOR_LEN;
pub const MAX_EDITION_LEN: usize = 1 + 32 + 8 + 200;

View File

@ -9,9 +9,9 @@ edition = "2018"
publish = false
[dependencies]
solana-client = "1.7.6"
solana-program = "1.7.6"
solana-sdk = "1.7.6"
solana-client = "1.7.8"
solana-program = "1.7.8"
solana-sdk = "1.7.8"
bincode = "1.3.2"
borsh = "0.9.1"
clap = "2.33.3"

View File

@ -15,7 +15,7 @@ test-bpf = []
[dependencies]
num-derive = "0.3"
num-traits = "0.2"
solana-program = "1.7.6"
solana-program = "1.7.8"
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
thiserror = "1.0"
borsh = "0.9.1"

View File

@ -9,9 +9,9 @@ edition = "2018"
publish = false
[dependencies]
solana-client = "1.7.6"
solana-program = "1.7.6"
solana-sdk = "1.7.6"
solana-client = "1.7.8"
solana-program = "1.7.8"
solana-sdk = "1.7.8"
bincode = "1.3.2"
borsh = "0.9.1"
clap = "2.33.3"

10
rust/tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"types": ["mocha"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}

1874
rust/yarn.lock Normal file

File diff suppressed because it is too large Load Diff