2021-08-31 12:16:16 -07:00
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as path from 'path';
|
|
|
|
import { program } from 'commander';
|
|
|
|
import * as anchor from '@project-serum/anchor';
|
|
|
|
import BN from 'bn.js';
|
2021-09-24 21:31:26 -07:00
|
|
|
import fetch from 'node-fetch';
|
2021-09-12 19:17:02 -07:00
|
|
|
|
|
|
|
import { fromUTF8Array, parsePrice } from './helpers/various';
|
2021-09-13 19:26:13 -07:00
|
|
|
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
2021-09-12 19:17:02 -07:00
|
|
|
import { PublicKey } from '@solana/web3.js';
|
2021-09-21 11:33:22 -07:00
|
|
|
import {
|
|
|
|
CACHE_PATH,
|
|
|
|
CONFIG_ARRAY_START,
|
|
|
|
CONFIG_LINE_SIZE,
|
|
|
|
EXTENSION_JSON,
|
|
|
|
EXTENSION_PNG,
|
|
|
|
} from './helpers/constants';
|
|
|
|
import {
|
|
|
|
getCandyMachineAddress,
|
|
|
|
loadCandyProgram,
|
|
|
|
loadWalletKey,
|
|
|
|
} from './helpers/accounts';
|
2021-09-07 12:02:10 -07:00
|
|
|
import { Config } from './types';
|
2021-09-12 19:17:02 -07:00
|
|
|
import { upload } from './commands/upload';
|
|
|
|
import { loadCache, saveCache } from './helpers/cache';
|
2021-09-21 11:33:22 -07:00
|
|
|
import { mint } from './commands/mint';
|
|
|
|
import { signMetadata } from './commands/sign';
|
|
|
|
import { signAllMetadataFromCandyMachine } from './commands/signAll';
|
2021-09-12 19:17:02 -07:00
|
|
|
import log from 'loglevel';
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-23 20:22:31 -07:00
|
|
|
program.version('0.0.2');
|
2021-08-31 12:16:16 -07:00
|
|
|
|
|
|
|
if (!fs.existsSync(CACHE_PATH)) {
|
|
|
|
fs.mkdirSync(CACHE_PATH);
|
|
|
|
}
|
|
|
|
|
2021-09-12 19:17:02 -07:00
|
|
|
log.setLevel(log.levels.INFO);
|
|
|
|
|
|
|
|
programCommand('upload')
|
2021-08-31 12:16:16 -07:00
|
|
|
.argument(
|
|
|
|
'<directory>',
|
|
|
|
'Directory containing images named from 0-n',
|
|
|
|
val => {
|
|
|
|
return fs.readdirSync(`${val}`).map(file => path.join(val, file));
|
|
|
|
},
|
|
|
|
)
|
2021-09-07 12:02:10 -07:00
|
|
|
.option('-n, --number <number>', 'Number of images to upload')
|
2021-09-23 20:22:31 -07:00
|
|
|
.option(
|
|
|
|
'-s, --storage <string>',
|
|
|
|
'Database to use for storage (arweave, ipfs)',
|
|
|
|
'arweave',
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'--ipfs-infura-project-id',
|
|
|
|
'Infura IPFS project id (required if using IPFS)',
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'--ipfs-infura-secret',
|
|
|
|
'Infura IPFS scret key (required if using IPFS)',
|
|
|
|
)
|
|
|
|
.option('--no-retain-authority', 'Do not retain authority to update metadata')
|
2021-08-31 12:16:16 -07:00
|
|
|
.action(async (files: string[], options, cmd) => {
|
2021-09-23 20:22:31 -07:00
|
|
|
const {
|
|
|
|
number,
|
|
|
|
keypair,
|
|
|
|
env,
|
|
|
|
cacheName,
|
|
|
|
storage,
|
|
|
|
ipfsInfuraProjectId,
|
|
|
|
ipfsInfuraSecret,
|
|
|
|
retainAuthority,
|
|
|
|
} = cmd.opts();
|
|
|
|
|
|
|
|
if (storage === 'ipfs' && (!ipfsInfuraProjectId || !ipfsInfuraSecret)) {
|
|
|
|
throw new Error(
|
|
|
|
'IPFS selected as storage option but Infura project id or secret key were not provided.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!(storage === 'arweave' || storage === 'ipfs')) {
|
|
|
|
throw new Error("Storage option must either be 'arweave' or 'ipfs'.");
|
|
|
|
}
|
|
|
|
const ipfsCredentials = {
|
|
|
|
projectId: ipfsInfuraProjectId,
|
|
|
|
secretKey: ipfsInfuraSecret,
|
|
|
|
};
|
2021-09-07 12:02:10 -07:00
|
|
|
|
|
|
|
const pngFileCount = files.filter(it => {
|
|
|
|
return it.endsWith(EXTENSION_PNG);
|
|
|
|
}).length;
|
|
|
|
const jsonFileCount = files.filter(it => {
|
|
|
|
return it.endsWith(EXTENSION_JSON);
|
|
|
|
}).length;
|
|
|
|
|
2021-09-13 15:50:44 -07:00
|
|
|
const parsedNumber = parseInt(number);
|
|
|
|
const elemCount = parsedNumber ? parsedNumber : pngFileCount;
|
|
|
|
|
2021-09-07 12:02:10 -07:00
|
|
|
if (pngFileCount !== jsonFileCount) {
|
2021-09-21 11:33:22 -07:00
|
|
|
throw new Error(
|
|
|
|
`number of png files (${pngFileCount}) is different than the number of json files (${jsonFileCount})`,
|
|
|
|
);
|
2021-09-07 12:02:10 -07:00
|
|
|
}
|
|
|
|
|
2021-09-13 15:50:44 -07:00
|
|
|
if (elemCount < pngFileCount) {
|
2021-09-21 11:33:22 -07:00
|
|
|
throw new Error(
|
|
|
|
`max number (${elemCount})cannot be smaller than the number of elements in the source folder (${pngFileCount})`,
|
|
|
|
);
|
2021-08-31 12:16:16 -07:00
|
|
|
}
|
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info(`Beginning the upload for ${elemCount} (png+json) pairs`);
|
2021-09-13 15:50:44 -07:00
|
|
|
|
2021-09-12 19:17:02 -07:00
|
|
|
const startMs = Date.now();
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info('started at: ' + startMs.toString());
|
2021-09-14 12:56:59 -07:00
|
|
|
let warn = false;
|
2021-09-21 11:33:22 -07:00
|
|
|
for (;;) {
|
|
|
|
const successful = await upload(
|
|
|
|
files,
|
|
|
|
cacheName,
|
|
|
|
env,
|
|
|
|
keypair,
|
|
|
|
elemCount,
|
2021-09-23 20:22:31 -07:00
|
|
|
storage,
|
|
|
|
retainAuthority,
|
|
|
|
ipfsCredentials,
|
2021-09-21 11:33:22 -07:00
|
|
|
);
|
2021-09-23 20:22:31 -07:00
|
|
|
|
2021-09-12 19:17:02 -07:00
|
|
|
if (successful) {
|
2021-09-14 12:56:59 -07:00
|
|
|
warn = false;
|
2021-09-12 19:17:02 -07:00
|
|
|
break;
|
|
|
|
} else {
|
2021-09-14 12:56:59 -07:00
|
|
|
warn = true;
|
2021-09-21 11:33:22 -07:00
|
|
|
log.warn('upload was not successful, rerunning');
|
2021-08-31 12:16:16 -07:00
|
|
|
}
|
|
|
|
}
|
2021-09-12 19:17:02 -07:00
|
|
|
const endMs = Date.now();
|
|
|
|
const timeTaken = new Date(endMs - startMs).toISOString().substr(11, 8);
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info(
|
|
|
|
`ended at: ${new Date(endMs).toString()}. time taken: ${timeTaken}`,
|
2021-08-31 12:16:16 -07:00
|
|
|
);
|
2021-09-21 11:33:22 -07:00
|
|
|
if (warn) {
|
|
|
|
log.info('not all images have been uplaoded, rerun this step.');
|
2021-09-07 12:02:10 -07:00
|
|
|
}
|
2021-09-21 11:33:22 -07:00
|
|
|
});
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
programCommand('verify').action(async (directory, cmd) => {
|
|
|
|
const { env, keypair, cacheName } = cmd.opts();
|
|
|
|
|
|
|
|
const cacheContent = loadCache(cacheName, env);
|
|
|
|
const walletKeyPair = loadWalletKey(keypair);
|
|
|
|
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
|
|
|
|
|
|
|
|
const configAddress = new PublicKey(cacheContent.program.config);
|
|
|
|
const config = await anchorProgram.provider.connection.getAccountInfo(
|
|
|
|
configAddress,
|
|
|
|
);
|
|
|
|
let allGood = true;
|
|
|
|
|
|
|
|
const keys = Object.keys(cacheContent.items);
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
log.debug('Looking at key ', i);
|
|
|
|
const key = keys[i];
|
|
|
|
const thisSlice = config.data.slice(
|
|
|
|
CONFIG_ARRAY_START + 4 + CONFIG_LINE_SIZE * i,
|
|
|
|
CONFIG_ARRAY_START + 4 + CONFIG_LINE_SIZE * (i + 1),
|
|
|
|
);
|
|
|
|
const name = fromUTF8Array([...thisSlice.slice(4, 36)]);
|
|
|
|
const uri = fromUTF8Array([...thisSlice.slice(40, 240)]);
|
|
|
|
const cacheItem = cacheContent.items[key];
|
|
|
|
if (!name.match(cacheItem.name) || !uri.match(cacheItem.link)) {
|
|
|
|
//leaving here for debugging reasons, but it's pretty useless. if the first upload fails - all others are wrong
|
|
|
|
// log.info(
|
|
|
|
// `Name (${name}) or uri (${uri}) didnt match cache values of (${cacheItem.name})` +
|
|
|
|
// `and (${cacheItem.link}). marking to rerun for image`,
|
|
|
|
// key,
|
|
|
|
// );
|
|
|
|
cacheItem.onChain = false;
|
|
|
|
allGood = false;
|
|
|
|
} else {
|
2021-09-24 21:31:26 -07:00
|
|
|
const json = await fetch(cacheItem.link);
|
|
|
|
if (json.status == 200 || json.status == 204 || json.status == 202) {
|
|
|
|
const body = await json.text();
|
|
|
|
const parsed = JSON.parse(body);
|
|
|
|
if (parsed.image) {
|
|
|
|
const check = await fetch(parsed.image);
|
|
|
|
if (
|
|
|
|
check.status == 200 ||
|
|
|
|
check.status == 204 ||
|
|
|
|
check.status == 202
|
|
|
|
) {
|
|
|
|
const text = await check.text();
|
|
|
|
if (!text.match(/Not found/i)) {
|
|
|
|
if (text.length == 0) {
|
|
|
|
log.debug(
|
|
|
|
'Name',
|
|
|
|
name,
|
|
|
|
'with',
|
|
|
|
uri,
|
|
|
|
'has zero length, failing',
|
|
|
|
);
|
|
|
|
cacheItem.onChain = false;
|
|
|
|
allGood = false;
|
|
|
|
} else {
|
|
|
|
log.debug('Name', name, 'with', uri, 'checked out');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.debug(
|
|
|
|
'Name',
|
|
|
|
name,
|
|
|
|
'with',
|
|
|
|
uri,
|
|
|
|
'never got uploaded to arweave, failing',
|
|
|
|
);
|
|
|
|
cacheItem.onChain = false;
|
|
|
|
allGood = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.debug(
|
|
|
|
'Name',
|
|
|
|
name,
|
|
|
|
'with',
|
|
|
|
uri,
|
|
|
|
'returned non-200 from uploader',
|
|
|
|
check.status,
|
|
|
|
);
|
|
|
|
cacheItem.onChain = false;
|
|
|
|
allGood = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.debug('Name', name, 'with', uri, 'lacked image in json, failing');
|
|
|
|
cacheItem.onChain = false;
|
|
|
|
allGood = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.debug('Name', name, 'with', uri, 'returned no json from link');
|
|
|
|
cacheItem.onChain = false;
|
|
|
|
allGood = false;
|
|
|
|
}
|
2021-09-07 12:02:10 -07:00
|
|
|
}
|
2021-09-21 11:33:22 -07:00
|
|
|
}
|
2021-09-07 12:02:10 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
if (!allGood) {
|
|
|
|
saveCache(cacheName, env, cacheContent);
|
2021-09-07 12:02:10 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
throw new Error(
|
|
|
|
`not all NFTs checked out. check out logs above for details`,
|
|
|
|
);
|
|
|
|
}
|
2021-09-07 12:02:10 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
const configData = (await anchorProgram.account.config.fetch(
|
|
|
|
configAddress,
|
|
|
|
)) as Config;
|
|
|
|
|
|
|
|
const lineCount = new BN(config.data.slice(247, 247 + 4), undefined, 'le');
|
|
|
|
|
|
|
|
log.info(
|
|
|
|
`uploaded (${lineCount.toNumber()}) out of (${
|
|
|
|
configData.data.maxNumberOfLines
|
|
|
|
})`,
|
|
|
|
);
|
|
|
|
if (configData.data.maxNumberOfLines > lineCount.toNumber()) {
|
|
|
|
throw new Error(
|
|
|
|
`predefined number of NFTs (${
|
|
|
|
configData.data.maxNumberOfLines
|
|
|
|
}) is smaller than the uploaded one (${lineCount.toNumber()})`,
|
2021-08-31 12:16:16 -07:00
|
|
|
);
|
2021-09-21 11:33:22 -07:00
|
|
|
} else {
|
|
|
|
log.info('ready to deploy!');
|
|
|
|
}
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
saveCache(cacheName, env, cacheContent);
|
|
|
|
});
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-14 12:54:51 -07:00
|
|
|
programCommand('verify_price')
|
|
|
|
.option('-p, --price <string>')
|
|
|
|
.option('--cache-path <string>')
|
|
|
|
.action(async (directory, cmd) => {
|
|
|
|
const { keypair, env, price, cacheName, cachePath } = cmd.opts();
|
|
|
|
const lamports = parsePrice(price);
|
|
|
|
|
|
|
|
if (isNaN(lamports)) {
|
|
|
|
return log.error(`verify_price requires a --price to be set`);
|
|
|
|
}
|
|
|
|
|
|
|
|
log.info(`Expected price is: ${lamports}`);
|
|
|
|
|
|
|
|
const cacheContent = loadCache(cacheName, env, cachePath);
|
|
|
|
|
|
|
|
if (!cacheContent) {
|
|
|
|
return log.error(
|
|
|
|
`No cache found, can't continue. Make sure you are in the correct directory where the assets are located or use the --cache-path option.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const walletKeyPair = loadWalletKey(keypair);
|
2021-09-21 11:33:22 -07:00
|
|
|
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
|
2021-09-14 12:54:51 -07:00
|
|
|
|
|
|
|
const [candyMachine] = await getCandyMachineAddress(
|
|
|
|
new PublicKey(cacheContent.program.config),
|
|
|
|
cacheContent.program.uuid,
|
|
|
|
);
|
|
|
|
|
|
|
|
const machine = await anchorProgram.account.candyMachine.fetch(
|
|
|
|
candyMachine,
|
|
|
|
);
|
|
|
|
|
|
|
|
//@ts-ignore
|
|
|
|
const candyMachineLamports = machine.data.price.toNumber();
|
|
|
|
|
|
|
|
log.info(`Candymachine price is: ${candyMachineLamports}`);
|
|
|
|
|
|
|
|
if (lamports != candyMachineLamports) {
|
|
|
|
throw new Error(`Expected price and CandyMachine's price do not match!`);
|
|
|
|
}
|
|
|
|
|
|
|
|
log.info(`Good to go!`);
|
|
|
|
});
|
|
|
|
|
2021-09-24 21:52:12 -07:00
|
|
|
programCommand('show')
|
|
|
|
.option('--cache-path <string>')
|
|
|
|
.action(async (directory, cmd) => {
|
|
|
|
const { keypair, env, cacheName, cachePath } = cmd.opts();
|
|
|
|
|
|
|
|
const cacheContent = loadCache(cacheName, env, cachePath);
|
|
|
|
|
|
|
|
if (!cacheContent) {
|
|
|
|
return log.error(
|
|
|
|
`No cache found, can't continue. Make sure you are in the correct directory where the assets are located or use the --cache-path option.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const walletKeyPair = loadWalletKey(keypair);
|
|
|
|
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
|
|
|
|
|
|
|
|
const [candyMachine] = await getCandyMachineAddress(
|
|
|
|
new PublicKey(cacheContent.program.config),
|
|
|
|
cacheContent.program.uuid,
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const machine = await anchorProgram.account.candyMachine.fetch(
|
|
|
|
candyMachine,
|
|
|
|
);
|
|
|
|
log.info('...Candy Machine...');
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('authority: ', machine.authority.toBase58());
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('wallet: ', machine.wallet.toBase58());
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('tokenMint: ', machine.tokenMint.toBase58());
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('config: ', machine.config.toBase58());
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('uuid: ', machine.data.uuid);
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('price: ', machine.data.price.toNumber());
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('itemsAvailable: ', machine.data.itemsAvailable.toNumber());
|
|
|
|
log.info(
|
|
|
|
'goLiveDate: ',
|
|
|
|
//@ts-ignore
|
|
|
|
machine.data.goLiveDate
|
|
|
|
? //@ts-ignore
|
|
|
|
new Date(machine.data.goLiveDate * 1000)
|
|
|
|
: 'N/A',
|
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
console.log('No machine found');
|
|
|
|
}
|
|
|
|
|
|
|
|
const config = await anchorProgram.account.config.fetch(
|
|
|
|
cacheContent.program.config,
|
|
|
|
);
|
|
|
|
log.info('...Config...');
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('authority: ', config.authority);
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('symbol: ', config.data.symbol);
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('sellerFeeBasisPoints: ', config.data.sellerFeeBasisPoints);
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('creators: ');
|
|
|
|
//@ts-ignore
|
|
|
|
config.data.creators.map(c =>
|
|
|
|
log.info(c.address.toBase58(), 'at', c.share, '%'),
|
|
|
|
),
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('maxSupply: ', config.data.maxSupply.toNumber());
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('retainAuthority: ', config.data.retainAuthority);
|
|
|
|
//@ts-ignore
|
|
|
|
log.info('maxNumberOfLines: ', config.data.maxNumberOfLines);
|
|
|
|
});
|
|
|
|
|
2021-09-12 19:17:02 -07:00
|
|
|
programCommand('create_candy_machine')
|
2021-09-21 11:33:22 -07:00
|
|
|
.option(
|
|
|
|
'-p, --price <string>',
|
|
|
|
'Price denominated in SOL or spl-token override',
|
|
|
|
'1',
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'-t, --spl-token <string>',
|
|
|
|
'SPL token used to price NFT mint. To use SOL leave this empty.',
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'-a, --spl-token-account <string>',
|
|
|
|
'SPL token account that receives mint payments. Only required if spl-token is specified.',
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'-s, --sol-treasury-account <string>',
|
|
|
|
'SOL account that receives mint payments.',
|
|
|
|
)
|
2021-08-31 12:16:16 -07:00
|
|
|
.action(async (directory, cmd) => {
|
2021-09-21 11:33:22 -07:00
|
|
|
const {
|
|
|
|
keypair,
|
|
|
|
env,
|
|
|
|
price,
|
|
|
|
cacheName,
|
|
|
|
splToken,
|
|
|
|
splTokenAccount,
|
|
|
|
solTreasuryAccount,
|
|
|
|
} = cmd.opts();
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-13 19:26:13 -07:00
|
|
|
let parsedPrice = parsePrice(price);
|
2021-09-07 12:02:10 -07:00
|
|
|
const cacheContent = loadCache(cacheName, env);
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-07 12:02:10 -07:00
|
|
|
const walletKeyPair = loadWalletKey(keypair);
|
2021-09-21 11:33:22 -07:00
|
|
|
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-13 19:26:13 -07:00
|
|
|
let wallet = walletKeyPair.publicKey;
|
|
|
|
const remainingAccounts = [];
|
|
|
|
if (splToken || splTokenAccount) {
|
2021-09-19 07:46:04 -07:00
|
|
|
if (solTreasuryAccount) {
|
2021-09-21 11:33:22 -07:00
|
|
|
throw new Error(
|
|
|
|
'If spl-token-account or spl-token is set then sol-treasury-account cannot be set',
|
|
|
|
);
|
2021-09-19 07:46:04 -07:00
|
|
|
}
|
2021-09-13 19:26:13 -07:00
|
|
|
if (!splToken) {
|
2021-09-21 11:33:22 -07:00
|
|
|
throw new Error(
|
|
|
|
'If spl-token-account is set, spl-token must also be set',
|
|
|
|
);
|
2021-09-13 19:26:13 -07:00
|
|
|
}
|
|
|
|
const splTokenKey = new PublicKey(splToken);
|
|
|
|
const splTokenAccountKey = new PublicKey(splTokenAccount);
|
|
|
|
if (!splTokenAccount) {
|
2021-09-21 11:33:22 -07:00
|
|
|
throw new Error(
|
|
|
|
'If spl-token is set, spl-token-account must also be set',
|
|
|
|
);
|
2021-09-13 19:26:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const token = new Token(
|
|
|
|
anchorProgram.provider.connection,
|
|
|
|
splTokenKey,
|
|
|
|
TOKEN_PROGRAM_ID,
|
2021-09-21 11:33:22 -07:00
|
|
|
walletKeyPair,
|
2021-09-13 19:26:13 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
const mintInfo = await token.getMintInfo();
|
|
|
|
if (!mintInfo.isInitialized) {
|
|
|
|
throw new Error(`The specified spl-token is not initialized`);
|
|
|
|
}
|
|
|
|
const tokenAccount = await token.getAccountInfo(splTokenAccountKey);
|
|
|
|
if (!tokenAccount.isInitialized) {
|
|
|
|
throw new Error(`The specified spl-token-account is not initialized`);
|
|
|
|
}
|
|
|
|
if (!tokenAccount.mint.equals(splTokenKey)) {
|
2021-09-21 11:33:22 -07:00
|
|
|
throw new Error(
|
|
|
|
`The spl-token-account's mint (${tokenAccount.mint.toString()}) does not match specified spl-token ${splTokenKey.toString()}`,
|
|
|
|
);
|
2021-09-13 19:26:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
wallet = splTokenAccountKey;
|
|
|
|
parsedPrice = parsePrice(price, 10 ** mintInfo.decimals);
|
2021-09-21 11:33:22 -07:00
|
|
|
remainingAccounts.push({
|
|
|
|
pubkey: splTokenKey,
|
|
|
|
isWritable: false,
|
|
|
|
isSigner: false,
|
|
|
|
});
|
2021-09-13 19:26:13 -07:00
|
|
|
}
|
|
|
|
|
2021-09-19 07:46:04 -07:00
|
|
|
if (solTreasuryAccount) {
|
|
|
|
const solAccountKey = new PublicKey(solTreasuryAccount);
|
|
|
|
wallet = solAccountKey;
|
|
|
|
}
|
|
|
|
|
2021-09-07 12:02:10 -07:00
|
|
|
const config = new PublicKey(cacheContent.program.config);
|
|
|
|
const [candyMachine, bump] = await getCandyMachineAddress(
|
2021-08-31 12:16:16 -07:00
|
|
|
config,
|
2021-09-07 12:02:10 -07:00
|
|
|
cacheContent.program.uuid,
|
2021-08-31 12:16:16 -07:00
|
|
|
);
|
2021-09-06 03:34:03 -07:00
|
|
|
await anchorProgram.rpc.initializeCandyMachine(
|
2021-08-31 12:16:16 -07:00
|
|
|
bump,
|
|
|
|
{
|
2021-09-07 12:02:10 -07:00
|
|
|
uuid: cacheContent.program.uuid,
|
2021-09-13 19:26:13 -07:00
|
|
|
price: new anchor.BN(parsedPrice),
|
2021-09-07 12:02:10 -07:00
|
|
|
itemsAvailable: new anchor.BN(Object.keys(cacheContent.items).length),
|
2021-08-31 12:16:16 -07:00
|
|
|
goLiveDate: null,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
accounts: {
|
|
|
|
candyMachine,
|
2021-09-13 19:26:13 -07:00
|
|
|
wallet,
|
2021-08-31 12:16:16 -07:00
|
|
|
config: config,
|
2021-09-07 12:02:10 -07:00
|
|
|
authority: walletKeyPair.publicKey,
|
|
|
|
payer: walletKeyPair.publicKey,
|
2021-08-31 12:16:16 -07:00
|
|
|
systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
|
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
|
|
|
},
|
|
|
|
signers: [],
|
2021-09-13 19:26:13 -07:00
|
|
|
remainingAccounts,
|
2021-08-31 12:16:16 -07:00
|
|
|
},
|
|
|
|
);
|
2021-09-12 19:17:02 -07:00
|
|
|
saveCache(cacheName, env, cacheContent);
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info(
|
|
|
|
`create_candy_machine finished. candy machine pubkey: ${candyMachine.toBase58()}`,
|
|
|
|
);
|
2021-08-31 12:16:16 -07:00
|
|
|
});
|
|
|
|
|
2021-09-15 11:07:04 -07:00
|
|
|
programCommand('update_candy_machine')
|
2021-09-07 12:02:10 -07:00
|
|
|
.option('-d, --date <string>', 'timestamp - eg "04 Dec 1995 00:12:00 GMT"')
|
2021-09-15 11:07:04 -07:00
|
|
|
.option('-p, --price <string>', 'SOL price')
|
2021-08-31 12:16:16 -07:00
|
|
|
.action(async (directory, cmd) => {
|
2021-09-15 11:07:04 -07:00
|
|
|
const { keypair, env, date, price, cacheName } = cmd.opts();
|
2021-09-07 12:02:10 -07:00
|
|
|
const cacheContent = loadCache(cacheName, env);
|
|
|
|
|
2021-09-15 11:07:04 -07:00
|
|
|
const secondsSinceEpoch = date ? Date.parse(date) / 1000 : null;
|
|
|
|
const lamports = price ? parsePrice(price) : null;
|
2021-09-07 12:02:10 -07:00
|
|
|
|
|
|
|
const walletKeyPair = loadWalletKey(keypair);
|
2021-09-21 11:33:22 -07:00
|
|
|
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
|
2021-09-07 12:02:10 -07:00
|
|
|
|
2021-09-12 19:17:02 -07:00
|
|
|
const [candyMachine] = await getCandyMachineAddress(
|
2021-09-07 12:02:10 -07:00
|
|
|
new PublicKey(cacheContent.program.config),
|
|
|
|
cacheContent.program.uuid,
|
|
|
|
);
|
|
|
|
const tx = await anchorProgram.rpc.updateCandyMachine(
|
2021-09-15 11:07:04 -07:00
|
|
|
lamports ? new anchor.BN(lamports) : null,
|
|
|
|
secondsSinceEpoch ? new anchor.BN(secondsSinceEpoch) : null,
|
2021-09-07 12:02:10 -07:00
|
|
|
{
|
|
|
|
accounts: {
|
|
|
|
candyMachine,
|
|
|
|
authority: walletKeyPair.publicKey,
|
|
|
|
},
|
|
|
|
},
|
2021-08-31 12:16:16 -07:00
|
|
|
);
|
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
if (date)
|
|
|
|
log.info(
|
|
|
|
` - updated startDate timestamp: ${secondsSinceEpoch} (${date})`,
|
|
|
|
);
|
|
|
|
if (lamports)
|
|
|
|
log.info(` - updated price: ${lamports} lamports (${price} SOL)`);
|
2021-09-15 11:07:04 -07:00
|
|
|
log.info('updated_candy_machine Done', tx);
|
2021-09-07 12:02:10 -07:00
|
|
|
});
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
programCommand('mint_one_token').action(async (directory, cmd) => {
|
|
|
|
const { keypair, env, cacheName } = cmd.opts();
|
2021-09-07 12:02:10 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
const cacheContent = loadCache(cacheName, env);
|
|
|
|
const configAddress = new PublicKey(cacheContent.program.config);
|
|
|
|
const tx = await mint(keypair, env, configAddress);
|
2021-08-31 12:16:16 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info('Done', tx);
|
|
|
|
});
|
2021-09-07 12:02:10 -07:00
|
|
|
|
2021-09-12 19:17:02 -07:00
|
|
|
programCommand('sign')
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
.option('-m, --metadata <string>', 'base58 metadata account id')
|
|
|
|
.action(async (directory, cmd) => {
|
2021-09-14 12:56:59 -07:00
|
|
|
const { keypair, env, metadata } = cmd.opts();
|
2021-09-12 19:17:02 -07:00
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
await signMetadata(metadata, keypair, env);
|
2021-09-12 19:17:02 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
function programCommand(name: string) {
|
|
|
|
return program
|
|
|
|
.command(name)
|
|
|
|
.option(
|
|
|
|
'-e, --env <string>',
|
|
|
|
'Solana cluster env name',
|
|
|
|
'devnet', //mainnet-beta, testnet, devnet
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'-k, --keypair <path>',
|
|
|
|
`Solana wallet location`,
|
|
|
|
'--keypair not provided',
|
|
|
|
)
|
|
|
|
.option('-l, --log-level <string>', 'log level', setLogLevel)
|
|
|
|
.option('-c, --cache-name <string>', 'Cache file name', 'temp');
|
|
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
function setLogLevel(value, prev) {
|
2021-09-14 12:56:59 -07:00
|
|
|
if (value === undefined || value === null) {
|
2021-09-21 11:33:22 -07:00
|
|
|
return;
|
2021-09-12 19:17:02 -07:00
|
|
|
}
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info('setting the log value to: ' + value);
|
2021-09-12 19:17:02 -07:00
|
|
|
log.setLevel(value);
|
|
|
|
}
|
|
|
|
|
2021-09-21 11:33:22 -07:00
|
|
|
programCommand('sign_candy_machine_metadata')
|
2021-09-14 12:56:59 -07:00
|
|
|
.option('-cndy, --candy-address <string>', 'Candy machine address', '')
|
|
|
|
.option('-b, --batch-size <string>', 'Batch size', '10')
|
|
|
|
.action(async (directory, cmd) => {
|
2021-09-21 11:33:22 -07:00
|
|
|
const { keypair, env, cacheName, batchSize } = cmd.opts();
|
|
|
|
let { candyAddress } = cmd.opts();
|
2021-09-14 12:56:59 -07:00
|
|
|
if (!keypair || keypair == '') {
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info('Keypair required!');
|
2021-09-14 12:56:59 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!candyAddress || candyAddress == '') {
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info('Candy machine address required! Using from saved list.');
|
2021-09-14 12:56:59 -07:00
|
|
|
const cacheContent = loadCache(cacheName, env);
|
|
|
|
const config = new PublicKey(cacheContent.program.config);
|
2021-09-21 11:33:22 -07:00
|
|
|
const candyMachine = (
|
|
|
|
await getCandyMachineAddress(config, cacheContent.program.uuid)
|
|
|
|
)[0];
|
2021-09-14 12:56:59 -07:00
|
|
|
candyAddress = candyMachine.toBase58();
|
|
|
|
}
|
2021-09-21 11:33:22 -07:00
|
|
|
const batchSizeParsed = parseInt(batchSize);
|
2021-09-14 12:56:59 -07:00
|
|
|
if (!parseInt(batchSize)) {
|
2021-09-21 11:33:22 -07:00
|
|
|
log.info('Batch size needs to be an integer!');
|
2021-09-14 12:56:59 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const walletKeyPair = loadWalletKey(keypair);
|
2021-09-21 11:33:22 -07:00
|
|
|
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
|
|
|
|
log.info('Creator pubkey: ', walletKeyPair.publicKey.toBase58());
|
|
|
|
log.info('Environment: ', env);
|
|
|
|
log.info('Candy machine address: ', candyAddress);
|
|
|
|
log.info('Batch Size: ', batchSizeParsed);
|
|
|
|
await signAllMetadataFromCandyMachine(
|
|
|
|
anchorProgram.provider.connection,
|
|
|
|
walletKeyPair,
|
|
|
|
candyAddress,
|
|
|
|
batchSizeParsed,
|
|
|
|
);
|
2021-09-14 12:56:59 -07:00
|
|
|
});
|
2021-08-31 12:16:16 -07:00
|
|
|
|
|
|
|
program.parse(process.argv);
|