COmmit to reproduce

This commit is contained in:
Jordan Prince 2021-09-10 19:50:50 -05:00
parent 43601098c2
commit 46693dc6dd
7 changed files with 247 additions and 757 deletions

View File

@ -34,7 +34,7 @@ import {
getMasterEdition,
getMetadata,
getTokenWallet,
loadAnchorProgram,
loadCandyProgram,
loadWalletKey,
} from './helpers/accounts';
import { Config } from './types';
@ -124,7 +124,7 @@ program
const SIZE = images.length;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
let config = cacheContent.program.config
? new PublicKey(cacheContent.program.config)
@ -321,7 +321,7 @@ program
const cacheContent = loadCache(cacheName, env);
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
const configAddress = new PublicKey(cacheContent.program.config);
const config = await anchorProgram.provider.connection.getAccountInfo(
@ -413,7 +413,7 @@ program
const cacheContent = loadCache(cacheName, env);
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
const config = new PublicKey(cacheContent.program.config);
const [candyMachine, bump] = await getCandyMachineAddress(
@ -466,7 +466,7 @@ program
const secondsSinceEpoch = (date ? Date.parse(date) : Date.now()) / 1000;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [candyMachine, _] = await getCandyMachineAddress(
@ -507,7 +507,7 @@ program
const mint = Keypair.generate();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
const anchorProgram = await loadCandyProgram(walletKeyPair, env);
const userTokenAccountAddress = await getTokenWallet(
walletKeyPair.publicKey,
mint.publicKey,

View File

@ -1,756 +1,178 @@
#!/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 { LAMPORTS_PER_SOL } from '@solana/web3.js';
import { sendTransactionWithRetryWithKeypair, fromUTF8Array } from './helper';
import { CACHE_PATH, TOKEN_PROGRAM_ID } from './helpers/constants';
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;
loadFairLaunchProgram,
loadWalletKey,
getTokenMint,
getFairLaunch,
getTreasury,
} from './helpers/accounts';
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));
},
.command('new_fair_launch')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-u, --url',
'Solana cluster url',
'https://api.mainnet-beta.solana.com/',
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.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 || {};
.option('-u, --uuid <string>', 'uuid')
.option('-f, --fee <string>', 'price range end', '2')
.option('-s, --price-range-start <string>', 'price range start', '1')
.option('-e, --price-range-end <string>', 'price range end', '2')
.option(
'-pos, --phase-one-start-date <string>',
'timestamp - eg "04 Dec 1995 00:12:00 GMT"',
)
.option(
'-poe, --phase-one-end-date <string>',
'timestamp - eg "04 Dec 1995 00:12:00 GMT"',
)
.option(
'-pte, --phase-two-end-date <string>',
'timestamp - eg "04 Dec 1995 00:12:00 GMT"',
)
.option('-ts, --tick-size <string>', 'tick size', '0.1')
.option('-n, --number-of-tokens <number>', 'Number of tokens to sell')
.option(
'-mint, --token-mint <string>',
'token mint to take as payment instead of sol',
)
.action(async (_, cmd) => {
const {
keypair,
env,
priceRangeStart,
priceRangeEnd,
phaseOneStartDate,
phaseOneEndDate,
phaseTwoEndDate,
tickSize,
numberOfTokens,
fee,
mint,
uuid,
} = cmd.opts();
const parsedNumber = parseInt(numberOfTokens);
let priceRangeStartNumber = parseFloat(priceRangeStart);
let priceRangeEndNumber = parseFloat(priceRangeEnd);
let tickSizeNumber = parseFloat(tickSize);
let feeNumber = parseFloat(fee);
const realUuid = uuid.slice(0, 6);
const phaseOneStartDateActual =
(phaseOneStartDate ? Date.parse(phaseOneStartDate) : Date.now()) / 1000;
const phaseOneEndDateActual =
(phaseOneEndDate ? Date.parse(phaseOneEndDate) : Date.now() + 86400000) /
1000;
const phaseTwoEndDateActual =
(phaseTwoEndDate
? Date.parse(phaseTwoEndDate)
: Date.now() + 2 * 86400000) / 1000;
if (!cacheContent.program) {
cacheContent.program = {};
if (!mint) {
priceRangeStartNumber = Math.ceil(
priceRangeStartNumber * LAMPORTS_PER_SOL,
);
priceRangeEndNumber = Math.ceil(priceRangeEndNumber * LAMPORTS_PER_SOL);
tickSizeNumber = Math.ceil(tickSizeNumber * LAMPORTS_PER_SOL);
feeNumber = Math.ceil(feeNumber * LAMPORTS_PER_SOL);
}
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 walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const [tokenMint, tokenBump] = await getTokenMint(
walletKeyPair.publicKey,
realUuid,
);
// 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',
);
console.info('transaction for arweave payment:', tx);
// data.append('tags', JSON.stringify(tags));
// payment transaction
const data = new FormData();
data.append('transaction', tx['txid']);
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);
}
}
}
}
const keys = Object.keys(cacheContent.items);
try {
await Promise.all(
chunks(Array.from(Array(keys.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 = keys[i];
return cacheContent.items[index]?.onChain;
});
const ind = keys[indexes[0]];
if (onChain.length != indexes.length) {
console.log(
'Writing indices ',
ind,
'-',
keys[indexes[indexes.length - 1]],
);
const txId = await anchorProgram.rpc.addConfigLines(
ind,
indexes.map(i => ({
uri: cacheContent.items[keys[i]].link,
name: cacheContent.items[keys[i]].name,
})),
{
accounts: {
config,
authority: walletKey.publicKey,
},
signers: [walletKey],
},
);
indexes.forEach(i => {
cacheContent.items[keys[i]] = {
...cacheContent.items[keys[i]],
onChain: true,
};
});
fs.writeFileSync(
path.join(CACHE_PATH, cacheName),
JSON.stringify(cacheContent),
);
}
}
const [fairLaunch, fairLaunchBump] = await getFairLaunch(tokenMint);
const [treasury, treasuryBump] = await getTreasury(tokenMint);
console.log('Mint is', mint);
const remainingAccounts = !mint
? []
: [
{
pubkey: new anchor.web3.PublicKey(mint),
isWritable: false,
isSigner: false,
},
),
);
} 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/`,
);
];
await anchorProgram.rpc.initializeFairLaunch(
fairLaunchBump,
treasuryBump,
tokenBump,
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,
uuid: realUuid,
priceRangeStart: new anchor.BN(priceRangeStartNumber),
priceRangeEnd: new anchor.BN(priceRangeEndNumber),
phaseOneStart: new anchor.BN(phaseOneStartDateActual),
phaseOneEnd: new anchor.BN(phaseOneEndDateActual),
phaseTwoEnd: new anchor.BN(phaseTwoEndDateActual),
tickSize: new anchor.BN(tickSizeNumber),
numberOfTokens: new anchor.BN(parsedNumber),
fee: new anchor.BN(feeNumber),
},
{
accounts: {
candyMachine,
wallet: walletKey.publicKey,
config: config,
authority: walletKey.publicKey,
payer: walletKey.publicKey,
fairLaunch,
tokenMint,
treasury,
authority: walletKeyPair.publicKey,
payer: walletKeyPair.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
remainingAccounts,
signers: [],
},
);
console.log(`Done: CANDYMACHINE: ${candyMachine.toBase58()}`);
console.log(`create fair launch Done: ${fairLaunch.toBase58()}`);
});
program
.command('mint_one_token')
.option('-k, --keypair <path>', `The purchaser's wallet key`)
.option('-c, --cache-name <path>', 'Cache file name')
.action(async (directory, cmd) => {
const solConnection = new anchor.web3.Connection(
`https://api.${ENV}.solana.com/`,
.command('show')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-u, --uuid <string>', 'uuid')
.action(async (files: string[], options, cmd) => {
const { env, uuid, keypair } = cmd.opts();
const realUuid = uuid.slice(0, 6);
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const tokenMint = (
await getTokenMint(walletKeyPair.publicKey, realUuid)
)[0];
const fairLaunch = (await getFairLaunch(tokenMint))[0];
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunch,
);
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 candy = await anchorProgram.account.candyMachine.fetch(candyMachine);
const metadata = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
const tx = await anchorProgram.rpc.mintNft({
accounts: {
config: config,
candyMachine: candyMachine,
payer: walletKey.publicKey,
//@ts-ignore
wallet: candy.wallet,
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);
console.log('Fair launch object', fairLaunchObj);
});
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 number = new BN(config.data.slice(247, 247 + 4), undefined, 'le');
console.log('Number', number.toNumber());
const keys = Object.keys(cachedContent.items);
for (let i = 0; i < keys.length; i++) {
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

@ -1,15 +1,16 @@
import {Keypair, PublicKey, SystemProgram} from '@solana/web3.js';
import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js';
import {
CANDY_MACHINE,
CANDY_MACHINE_PROGRAM_ID,
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
TOKEN_METADATA_PROGRAM_ID,
TOKEN_PROGRAM_ID,
FAIR_LAUNCH_PROGRAM_ID,
} from './constants';
import * as anchor from '@project-serum/anchor';
import fs from 'fs';
import BN from "bn.js";
import {createConfigAccount} from "./instructions";
import BN from 'bn.js';
import { createConfigAccount } from './instructions';
export const createConfig = async function (
anchorProgram: anchor.Program,
@ -93,6 +94,39 @@ export const getConfig = async (
);
};
export const getTokenMint = async (
authority: anchor.web3.PublicKey,
uuid: string,
): Promise<[anchor.web3.PublicKey, number]> => {
return await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from('fair_launch'),
authority.toBuffer(),
Buffer.from('mint'),
Buffer.from(uuid),
],
FAIR_LAUNCH_PROGRAM_ID,
);
};
export const getFairLaunch = async (
tokenMint: anchor.web3.PublicKey,
): Promise<[anchor.web3.PublicKey, number]> => {
return await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from('fair_launch'), tokenMint.toBuffer()],
FAIR_LAUNCH_PROGRAM_ID,
);
};
export const getTreasury = async (
tokenMint: anchor.web3.PublicKey,
): Promise<[anchor.web3.PublicKey, number]> => {
return await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from('fair_launch'), tokenMint.toBuffer(), Buffer.from('treasury')],
FAIR_LAUNCH_PROGRAM_ID,
);
};
export const getMetadata = async (
mint: anchor.web3.PublicKey,
): Promise<anchor.web3.PublicKey> => {
@ -130,7 +164,7 @@ export function loadWalletKey(keypair): Keypair {
);
}
export async function loadAnchorProgram(walletKeyPair: Keypair, env: string) {
export async function loadCandyProgram(walletKeyPair: Keypair, env: string) {
const solConnection = new anchor.web3.Connection(
`https://api.${env}.solana.com/`,
);
@ -142,3 +176,19 @@ export async function loadAnchorProgram(walletKeyPair: Keypair, env: string) {
return new anchor.Program(idl, CANDY_MACHINE_PROGRAM_ID, provider);
}
export async function loadFairLaunchProgram(
walletKeyPair: Keypair,
env: string,
) {
const solConnection = new anchor.web3.Connection(
`https://api.${env}.solana.com/`,
);
const walletWrapper = new anchor.Wallet(walletKeyPair);
const provider = new anchor.Provider(solConnection, walletWrapper, {
preflightCommitment: 'recent',
});
const idl = await anchor.Program.fetchIdl(FAIR_LAUNCH_PROGRAM_ID, provider);
return new anchor.Program(idl, FAIR_LAUNCH_PROGRAM_ID, provider);
}

View File

@ -17,6 +17,9 @@ export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
export const TOKEN_PROGRAM_ID = new PublicKey(
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
);
export const FAIR_LAUNCH_PROGRAM_ID = new PublicKey(
'7HmfyvWK7LDohUL9TDAuGv9VFZHUce1SgYMkwti1xWwF',
);
export const CONFIG_ARRAY_START =
32 + // authority

View File

@ -8,14 +8,16 @@ use {
},
anchor_lang::{
prelude::*,
solana_program::{program_pack::Pack, system_program, system_instruction, program::{invoke, invoke_signed}},
solana_program::{
program::{invoke, invoke_signed},
program_pack::Pack,
system_instruction, system_program,
},
AnchorDeserialize, AnchorSerialize,
},
anchor_spl::token::Mint,
spl_token::{instruction::initialize_account2, state::Account},
std::{str::FromStr, convert::TryFrom},
std::str::FromStr,
};
pub const PREFIX: &str = "fair_launch";
@ -57,6 +59,13 @@ pub mod fair_launch {
fair_launch.treasury = *treasury_info.key;
assert_derivation(ctx.program_id, treasury_info, treasury_seeds)?;
let signer_seeds = &[
PREFIX.as_bytes(),
token_mint_key.as_ref(),
TREASURY.as_bytes(),
&[fair_launch.treasury_bump],
];
if ctx.remaining_accounts.len() > 0 {
let treasury_mint_info = &ctx.remaining_accounts[0];
let _treasury_mint: spl_token::state::Mint = assert_initialized(&treasury_mint_info)?;
@ -67,13 +76,6 @@ pub mod fair_launch {
// make the treasury token account
let signer_seeds = &[
PREFIX.as_bytes(),
token_mint_key.as_ref(),
TREASURY.as_bytes(),
&[fair_launch.treasury_bump],
];
create_or_allocate_account_raw(
*ctx.accounts.token_program.key,
treasury_info,
@ -104,10 +106,16 @@ pub mod fair_launch {
// Nothing to do but check that it does not already exist, we can begin transferring sol to it.
if !treasury_info.data_is_empty()
|| treasury_info.lamports() > 0
|| treasury_info.owner != ctx.program_id
|| treasury_info.owner != ctx.accounts.system_program.key
{
return Err(ErrorCode::TreasuryAlreadyExists.into());
}
invoke_signed(
&system_instruction::assign(treasury_info.key, &ctx.program_id),
&[ctx.accounts.system_program.clone(), treasury_info.clone()],
&[signer_seeds],
)?;
}
// now we do the counts.
@ -273,6 +281,10 @@ pub mod fair_launch {
adjust_counts(fair_launch, amount, None)?;
let charged_amount = amount
.checked_add(fair_launch.data.fee)
.ok_or(ErrorCode::NumericalOverflowError)?;
if let Some(treasury_mint) = fair_launch.treasury_mint {
let treasury_mint_info = &ctx.remaining_accounts[0];
let _treasury_mint: spl_token::state::Mint = assert_initialized(&treasury_mint_info)?;
@ -310,7 +322,7 @@ pub mod fair_launch {
],
)?;
if buyer_token_account.amount < amount {
if buyer_token_account.amount < charged_amount {
return Err(ErrorCode::NotEnoughTokens.into());
}
@ -320,15 +332,15 @@ pub mod fair_launch {
authority: transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program.clone(),
amount: amount,
amount: charged_amount,
})?;
} else {
if buyer.lamports() < amount {
if buyer.lamports() < charged_amount {
return Err(ErrorCode::NotEnoughSOL.into());
}
invoke(
&system_instruction::transfer(buyer.key, ctx.accounts.treasury.key, amount),
&system_instruction::transfer(buyer.key, ctx.accounts.treasury.key, charged_amount),
&[
buyer.clone(),
ctx.accounts.treasury.clone(),
@ -393,9 +405,8 @@ pub mod fair_launch {
],
)?;
if fair_launch_ticket.state.clone() as u8 != FairLaunchTicketState::Unpunched as u8 {
return Err(ErrorCode::InvalidFairLaunchTicketState.into())
return Err(ErrorCode::InvalidFairLaunchTicketState.into());
}
if fair_launch.phase_three_started {
@ -421,9 +432,8 @@ pub mod fair_launch {
}
}
} else if !buyer.is_signer {
return Err(ErrorCode::DuringPhaseTwoAndOneBuyerMustBeSigner.into())
return Err(ErrorCode::DuringPhaseTwoAndOneBuyerMustBeSigner.into());
}
if amount != 0 {
assert_valid_amount(&fair_launch.data, amount)?;
@ -568,7 +578,7 @@ pub mod fair_launch {
let token_mint = &ctx.accounts.token_mint;
if fair_launch_ticket.state.clone() as u8 != FairLaunchTicketState::Unpunched as u8 {
return Err(ErrorCode::InvalidFairLaunchTicketState.into())
return Err(ErrorCode::InvalidFairLaunchTicketState.into());
}
let (mask, index) = get_mask_and_index_for_seq(fair_launch_ticket.seq)?;
@ -633,12 +643,14 @@ pub mod fair_launch {
let treasury = &mut ctx.accounts.treasury;
let authority = &mut ctx.accounts.authority;
if fair_launch.number_tickets_sold > fair_launch.number_tickets_dropped + fair_launch.number_tickets_punched {
return Err(ErrorCode::CannotCashOutUntilAllRefundsAndPunchesHaveBeenProcessed.into())
}
if fair_launch.number_tickets_sold
> fair_launch.number_tickets_dropped + fair_launch.number_tickets_punched
{
return Err(ErrorCode::CannotCashOutUntilAllRefundsAndPunchesHaveBeenProcessed.into());
}
if !fair_launch.phase_three_started {
return Err(ErrorCode::CannotCashOutUntilPhaseThree.into())
return Err(ErrorCode::CannotCashOutUntilPhaseThree.into());
}
let signer_seeds = [
@ -652,7 +664,8 @@ pub mod fair_launch {
let _treasury_mint: spl_token::state::Mint = assert_initialized(&treasury_mint_info)?;
let authority_token_account_info = &ctx.remaining_accounts[1];
let authority_token_account: Account = assert_initialized(&authority_token_account_info)?;
let authority_token_account: Account =
assert_initialized(&authority_token_account_info)?;
let treasury_account: Account = assert_initialized(treasury)?;
let token_program = &ctx.remaining_accounts[2];
@ -707,7 +720,6 @@ pub mod fair_launch {
)?;
}
Ok(())
}
}
@ -719,6 +731,7 @@ pub struct InitializeFairLaunch<'info> {
fair_launch: ProgramAccount<'info, FairLaunch>,
#[account(init, seeds=[PREFIX.as_bytes(), authority.key.as_ref(), MINT.as_bytes(), data.uuid.as_bytes()], mint::authority=fair_launch, mint::decimals=0, payer=payer, bump=token_mint_bump)]
token_mint: CpiAccount<'info, Mint>,
#[account(mut)]
treasury: AccountInfo<'info>,
#[account(constraint= authority.data_is_empty() && authority.lamports() > 0)]
authority: AccountInfo<'info>,
@ -902,6 +915,7 @@ pub const FAIR_LAUNCH_SPACE_VEC_START: usize = 8 + // discriminator
8 + // phase two end
8 + // tick size
8 + // number of tokens
8 + // fee
8 + // number of tickets unseq'ed
8 + // number of tickets sold
8 + // number of tickets dropped
@ -934,6 +948,7 @@ pub struct FairLaunchData {
pub phase_two_end: i64,
pub tick_size: u64,
pub number_of_tokens: u64,
pub fee: u64,
}
#[account]
@ -1060,5 +1075,5 @@ pub enum ErrorCode {
#[msg("Cannot cash out until all refunds and punches (permissionless calls) have been processed. Use the CLI.")]
CannotCashOutUntilAllRefundsAndPunchesHaveBeenProcessed,
#[msg("Cannot cash out until phase three")]
CannotCashOutUntilPhaseThree
CannotCashOutUntilPhaseThree,
}

View File

@ -183,8 +183,8 @@ pub fn assert_data_valid(data: &FairLaunchData) -> ProgramResult {
}
let difference = data
.price_range_start
.checked_sub(data.price_range_end)
.price_range_end
.checked_sub(data.price_range_start)
.ok_or(ErrorCode::NumericalOverflowError)?;
let possible_valid_user_prices = difference
.checked_div(data.tick_size)

File diff suppressed because one or more lines are too long