metaplex/js/packages/cli/src/fair-launch-cli.ts

2929 lines
85 KiB
JavaScript
Executable File

#!/usr/bin/env node
import * as fs from 'fs';
import { program } from 'commander';
import * as anchor from '@project-serum/anchor';
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
import { Token, MintLayout } from '@solana/spl-token';
import {
CACHE_PATH,
FAIR_LAUNCH_PROGRAM_ID,
TOKEN_METADATA_PROGRAM_ID,
TOKEN_PROGRAM_ID,
} from './helpers/constants';
import {
loadFairLaunchProgram,
loadWalletKey,
getTokenMint,
getFairLaunch,
getTreasury,
getFairLaunchTicket,
getAtaForMint,
getFairLaunchTicketSeqLookup,
getFairLaunchLotteryBitmap,
getMetadata,
getParticipationMint,
getParticipationToken,
getMasterEdition,
getEditionMarkPda,
} from './helpers/accounts';
import { chunks, getMultipleAccounts, sleep } from './helpers/various';
import { createAssociatedTokenAccountInstruction } from './helpers/instructions';
import { sendTransactionWithRetryWithKeypair } from './helpers/transactions';
program.version('0.0.1');
if (!fs.existsSync(CACHE_PATH)) {
fs.mkdirSync(CACHE_PATH);
}
const FAIR_LAUNCH_LOTTERY_SIZE =
8 + // discriminator
32 + // fair launch
1 + // bump
8; // size of bitmask ones
program
.command('new_fair_launch')
.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')
.option('-f, --fee <string>', 'fee', '2')
.option('-s, --price-range-start <string>', 'price range start', '1')
.option('-pe, --price-range-end <string>', 'price range end', '2')
.option(
'-arbp, --anti-rug-reserve-bp <string>',
'optional anti-rug treasury reserve basis points (1-10000)',
)
.option(
'-atc, --anti-rug-token-requirement <string>',
'optional anti-rug token requirement when reserve opens - 100 means 100 tokens remaining out of total supply',
)
.option(
'-sd, --self-destruct-date <string>',
'optional date when funds from anti-rug setting will be returned - eg "04 Dec 1995 00:12:00 GMT"',
)
.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('-ld, --lottery-duration <string>', 'seconds eg 86400')
.option('-ts, --tick-size <string>', 'tick size', '0.1')
.option('-n, --number-of-tokens <number>', 'Number of tokens to sell')
.option(
'-mint, --treasury-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,
uuid,
selfDestructDate,
antiRugTokenRequirement,
antiRugReserveBp,
lotteryDuration,
treasuryMint,
} = cmd.opts();
const antiRugTokenRequirementNumber = antiRugTokenRequirement
? parseInt(antiRugTokenRequirement)
: null;
const antiRugReserveBpNumber = antiRugReserveBp
? parseInt(antiRugReserveBp)
: null;
const selfDestructDateActual = selfDestructDate
? Date.parse(selfDestructDate) / 1000
: null;
const antiRug =
antiRugTokenRequirementNumber &&
antiRugReserveBpNumber &&
selfDestructDateActual
? {
reserveBp: antiRugReserveBpNumber,
tokenRequirement: new anchor.BN(antiRugTokenRequirementNumber),
selfDestructDate: new anchor.BN(selfDestructDateActual),
}
: null;
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;
const lotteryDurationActual = lotteryDuration ? lotteryDuration : 86400;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
if (!treasuryMint) {
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);
} else {
const token = new Token(
anchorProgram.provider.connection,
//@ts-ignore
new anchor.web3.PublicKey(treasuryMint),
TOKEN_PROGRAM_ID,
walletKeyPair,
);
const mintInfo = await token.getMintInfo();
const mantissa = 10 ** mintInfo.decimals;
priceRangeStartNumber = Math.ceil(priceRangeStartNumber * mantissa);
priceRangeEndNumber = Math.ceil(priceRangeEndNumber * mantissa);
tickSizeNumber = Math.ceil(tickSizeNumber * mantissa);
feeNumber = Math.ceil(feeNumber * mantissa);
}
const [tokenMint, tokenBump] = await getTokenMint(
walletKeyPair.publicKey,
realUuid,
);
const [fairLaunch, fairLaunchBump] = await getFairLaunch(tokenMint);
const [treasury, treasuryBump] = await getTreasury(tokenMint);
const remainingAccounts = !treasuryMint
? []
: [
{
pubkey: new anchor.web3.PublicKey(treasuryMint),
isWritable: false,
isSigner: false,
},
];
await anchorProgram.rpc.initializeFairLaunch(
fairLaunchBump,
treasuryBump,
tokenBump,
{
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),
lotteryDuration: new anchor.BN(lotteryDurationActual),
tickSize: new anchor.BN(tickSizeNumber),
numberOfTokens: new anchor.BN(parsedNumber),
fee: new anchor.BN(feeNumber),
antiRugSetting: antiRug,
},
{
accounts: {
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(`create fair launch Done: ${fairLaunch.toBase58()}`);
});
program
.command('update_fair_launch')
.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')
.option('-f, --fee <string>', 'price range end', '2')
.option('-s, --price-range-start <string>', 'price range start', '1')
.option('-pe, --price-range-end <string>', 'price range end', '2')
.option(
'-arbp, --anti-rug-reserve-bp <string>',
'optional anti-rug treasury reserve basis points (1-10000)',
)
.option(
'-atc, --anti-rug-token-requirement <string>',
'optional anti-rug token requirement when reserve opens - 100 means 100 tokens remaining out of total supply',
)
.option(
'-sd, --self-destruct-date <string>',
'optional date when funds from anti-rug setting will be returned - eg "04 Dec 1995 00:12:00 GMT"',
)
.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('-ld, --lottery-duration <string>', 'seconds eg 86400')
.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,
selfDestructDate,
antiRugTokenRequirement,
antiRugReserveBp,
lotteryDuration,
} = cmd.opts();
const antiRugTokenRequirementNumber = antiRugTokenRequirement
? parseInt(antiRugTokenRequirement)
: null;
const antiRugReserveBpNumber = antiRugReserveBp
? parseInt(antiRugReserveBp)
: null;
const selfDestructDateActual = selfDestructDate
? Date.parse(selfDestructDate) / 1000
: null;
const antiRug =
antiRugTokenRequirementNumber &&
antiRugReserveBpNumber &&
selfDestructDateActual
? {
reserveBp: antiRugReserveBpNumber,
tokenRequirement: new anchor.BN(antiRugTokenRequirementNumber),
selfDestructDate: new anchor.BN(selfDestructDateActual),
}
: null;
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;
const lotteryDurationActual = lotteryDuration ? lotteryDuration : 86400;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
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);
} else {
const token = new Token(
anchorProgram.provider.connection,
//@ts-ignore
fairLaunchObj.treasuryMint,
TOKEN_PROGRAM_ID,
walletKeyPair,
);
const mintInfo = await token.getMintInfo();
const mantissa = 10 ** mintInfo.decimals;
priceRangeStartNumber = Math.ceil(priceRangeStartNumber * mantissa);
priceRangeEndNumber = Math.ceil(priceRangeEndNumber * mantissa);
tickSizeNumber = Math.ceil(tickSizeNumber * mantissa);
feeNumber = Math.ceil(feeNumber * mantissa);
}
const tokenMint = (
await getTokenMint(walletKeyPair.publicKey, realUuid)
)[0];
const fairLaunch = (await getFairLaunch(tokenMint))[0];
await anchorProgram.rpc.updateFairLaunch(
{
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),
lotteryDuration: new anchor.BN(lotteryDurationActual),
tickSize: new anchor.BN(tickSizeNumber),
numberOfTokens: new anchor.BN(parsedNumber),
fee: new anchor.BN(feeNumber),
antiRugSetting: antiRug,
},
{
accounts: {
fairLaunch,
authority: walletKeyPair.publicKey,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
},
);
console.log(`Updated fair launch Done: ${fairLaunch.toBase58()}`);
});
program
.command('purchase_ticket')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-a, --amount <string>', 'amount')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch, amount } = cmd.opts();
let amountNumber = parseFloat(amount);
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const [fairLaunchTicket, bump] = await getFairLaunchTicket(
//@ts-ignore
fairLaunchObj.tokenMint,
walletKeyPair.publicKey,
);
const remainingAccounts = [];
const instructions = [];
const signers = [];
//@ts-ignore
const tokenAta = fairLaunchObj.treasuryMint
? (
await getAtaForMint(
//@ts-ignore
fairLaunchObj.treasuryMint,
walletKeyPair.publicKey,
)
)[0]
: undefined;
//@ts-ignore
if (!fairLaunchObj.treasuryMint) {
amountNumber = Math.ceil(amountNumber * LAMPORTS_PER_SOL);
} else {
const transferAuthority = anchor.web3.Keypair.generate();
signers.push(transferAuthority);
const token = new Token(
anchorProgram.provider.connection,
//@ts-ignore
fairLaunchObj.treasuryMint,
TOKEN_PROGRAM_ID,
walletKeyPair,
);
const mintInfo = await token.getMintInfo();
amountNumber = Math.ceil(amountNumber * 10 ** mintInfo.decimals);
instructions.push(
Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
//@ts-ignore
tokenAta,
transferAuthority.publicKey,
walletKeyPair.publicKey,
[],
amountNumber * 10 ** mintInfo.decimals +
//@ts-ignore
fairLaunchObj.data.fee.toNumber(),
),
);
remainingAccounts.push({
//@ts-ignore
pubkey: fairLaunchObj.treasuryMint,
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: tokenAta,
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: transferAuthority.publicKey,
isWritable: false,
isSigner: true,
});
remainingAccounts.push({
pubkey: TOKEN_PROGRAM_ID,
isWritable: false,
isSigner: false,
});
}
instructions.push(
await anchorProgram.instruction.purchaseTicket(
bump,
new anchor.BN(amountNumber),
{
accounts: {
fairLaunchTicket,
fairLaunch,
//@ts-ignore
treasury: fairLaunchObj.treasury,
buyer: walletKeyPair.publicKey,
payer: walletKeyPair.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
remainingAccounts,
},
),
);
if (tokenAta) {
instructions.push(
Token.createRevokeInstruction(
TOKEN_PROGRAM_ID,
tokenAta,
walletKeyPair.publicKey,
[],
),
);
}
await sendTransactionWithRetryWithKeypair(
anchorProgram.provider.connection,
walletKeyPair,
instructions,
signers,
'max',
);
console.log(
`create fair launch ticket Done: ${fairLaunchTicket.toBase58()}. Trying to create seq now...we may or may not get a validator with data on chain. Either way, your ticket is secure.`,
);
let fairLaunchTicketObj;
for (let i = 0; i < 10; i++) {
await sleep(5000);
try {
fairLaunchTicketObj =
await anchorProgram.account.fairLaunchTicket.fetch(fairLaunchTicket);
break;
} catch (e) {
console.log('Not found. Trying again...');
}
}
const [fairLaunchTicketSeqLookup, seqBump] =
await getFairLaunchTicketSeqLookup(
//@ts-ignore
fairLaunchObj.tokenMint,
//@ts-ignore
fairLaunchTicketObj.seq,
);
await anchorProgram.rpc.createTicketSeq(seqBump, {
accounts: {
fairLaunchTicketSeqLookup,
fairLaunch,
fairLaunchTicket,
payer: walletKeyPair.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [],
});
console.log('Created seq');
});
program
.command('mint_from_dummy')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option(
'-d, --destination <path>',
`Destination wallet location`,
'--destination not provided',
)
.option('-a, --amount <string>', 'amount')
.option('-m, --mint <string>', 'mint')
.action(async (_, cmd) => {
const { env, keypair, amount, destination, mint } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const amountNumber = parseFloat(amount);
const mintKey = new anchor.web3.PublicKey(mint);
const dest = new anchor.web3.PublicKey(destination);
const token = (await getAtaForMint(mintKey, dest))[0];
const instructions = [];
const tokenApp = new Token(
anchorProgram.provider.connection,
//@ts-ignore
new anchor.web3.PublicKey(mint),
TOKEN_PROGRAM_ID,
walletKeyPair,
);
const mintInfo = await tokenApp.getMintInfo();
const mantissa = 10 ** mintInfo.decimals;
const assocToken = await anchorProgram.provider.connection.getAccountInfo(
token,
);
if (!assocToken) {
instructions.push(
createAssociatedTokenAccountInstruction(
token,
walletKeyPair.publicKey,
dest,
mintKey,
),
);
}
instructions.push(
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mintKey,
token,
walletKeyPair.publicKey,
[],
amountNumber * mantissa,
),
);
await sendTransactionWithRetryWithKeypair(
anchorProgram.provider.connection,
walletKeyPair,
instructions,
[],
'single',
);
console.log(`Minted ${amount} to ${token.toBase58()}.`);
});
program
.command('create_dummy_payment_mint')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.action(async (_, cmd) => {
const { env, keypair } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const mint = anchor.web3.Keypair.generate();
const token = (
await getAtaForMint(mint.publicKey, walletKeyPair.publicKey)
)[0];
const instructions: anchor.web3.TransactionInstruction[] = [
anchor.web3.SystemProgram.createAccount({
fromPubkey: walletKeyPair.publicKey,
newAccountPubkey: mint.publicKey,
space: MintLayout.span,
lamports:
await anchorProgram.provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span,
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
6,
walletKeyPair.publicKey,
walletKeyPair.publicKey,
),
createAssociatedTokenAccountInstruction(
token,
walletKeyPair.publicKey,
walletKeyPair.publicKey,
mint.publicKey,
),
];
const signers = [mint];
await sendTransactionWithRetryWithKeypair(
anchorProgram.provider.connection,
walletKeyPair,
instructions,
signers,
'single',
);
console.log(`create mint Done: ${mint.publicKey.toBase58()}.`);
});
async function adjustTicket({
amountNumber,
fairLaunchObj,
adjuster,
fairLaunch,
fairLaunchTicket,
fairLaunchLotteryBitmap,
anchorProgram,
payer,
adjustMantissa,
}: {
amountNumber: number;
fairLaunchObj: any;
payer: anchor.web3.Keypair;
adjuster: anchor.web3.PublicKey;
fairLaunch: anchor.web3.PublicKey;
fairLaunchTicket: anchor.web3.PublicKey;
fairLaunchLotteryBitmap: anchor.web3.PublicKey;
anchorProgram: anchor.Program;
adjustMantissa: boolean;
}) {
const remainingAccounts = [];
const instructions = [];
const signers = [];
//@ts-ignore
const tokenAta = fairLaunchObj.treasuryMint
? (
await getAtaForMint(
//@ts-ignore
fairLaunchObj.treasuryMint,
adjuster,
)
)[0]
: undefined;
//@ts-ignore
if (!fairLaunchObj.treasuryMint) {
if (adjustMantissa)
amountNumber = Math.ceil(amountNumber * LAMPORTS_PER_SOL);
} else {
const transferAuthority = anchor.web3.Keypair.generate();
signers.push(transferAuthority);
const token = new Token(
anchorProgram.provider.connection,
fairLaunchObj.treasuryMint,
TOKEN_PROGRAM_ID,
payer,
);
const mintInfo = await token.getMintInfo();
if (adjustMantissa)
amountNumber = Math.ceil(amountNumber * 10 ** mintInfo.decimals);
if (amountNumber > 0) {
instructions.push(
Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
tokenAta,
transferAuthority.publicKey,
adjuster,
[],
//@ts-ignore
amountNumber,
),
);
}
remainingAccounts.push({
//@ts-ignore
pubkey: fairLaunchObj.treasuryMint,
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: tokenAta,
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: transferAuthority.publicKey,
isWritable: false,
isSigner: true,
});
remainingAccounts.push({
pubkey: TOKEN_PROGRAM_ID,
isWritable: false,
isSigner: false,
});
}
instructions.push(
await anchorProgram.instruction.adjustTicket(new anchor.BN(amountNumber), {
accounts: {
fairLaunchTicket,
fairLaunch,
fairLaunchLotteryBitmap,
//@ts-ignore
treasury: fairLaunchObj.treasury,
systemProgram: anchor.web3.SystemProgram.programId,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
//__private: { logAccounts: true },
remainingAccounts: [
{
pubkey: adjuster,
isSigner: adjuster.equals(payer.publicKey),
isWritable: true,
},
...remainingAccounts,
],
}),
);
//@ts-ignore
if (fairLaunchObj.treasuryMint && amountNumber > 0) {
instructions.push(
Token.createRevokeInstruction(
FAIR_LAUNCH_PROGRAM_ID,
tokenAta,
payer.publicKey,
[],
),
);
}
await sendTransactionWithRetryWithKeypair(
anchorProgram.provider.connection,
payer,
instructions,
signers,
'single',
);
console.log(
`update fair launch ticket Done: ${fairLaunchTicket.toBase58()}.`,
);
}
program
.command('update_participation_nft')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-n, --name <string>', 'name')
.option('-s, --symbol <string>', 'symbol')
.option('-u, --uri <string>', 'uri')
.option(
'-sfbp, --seller-fee-basis-points <string>',
'seller fee basis points',
)
.option(
'-m, --participation-modulo <string>',
'1 if everybody gets it, 4 if only 1 in 4 get it, etc',
)
.option(
'-c, --creators <string>',
'comma separated creator wallets like wallet1,73,true,wallet2,27,false where its wallet, then share, then verified true/false',
)
.option('-nm, --is_not_mutable', 'is not mutable')
.action(async (_, cmd) => {
const {
env,
keypair,
fairLaunch,
name,
symbol,
uri,
sellerFeeBasisPoints,
creators,
isNotMutable,
participationModulo,
} = cmd.opts();
const sellerFeeBasisPointsNumber = parseInt(sellerFeeBasisPoints);
const participationModuloNumber = parseInt(participationModulo);
const creatorsListPre = creators ? creators.split(',') : [];
const creatorsList = [];
for (let i = 0; i < creatorsListPre.length; i += 3) {
creatorsList.push({
address: new anchor.web3.PublicKey(creatorsListPre[i]),
share: parseInt(creatorsListPre[i + 1]),
verified: creatorsListPre[i + 2] == 'true' ? true : false,
});
}
const isMutableBool = isNotMutable ? false : true;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const participationMint = (
await getParticipationMint(
//@ts-ignore
fairLaunchObj.authority,
//@ts-ignore
fairLaunchObj.data.uuid,
)
)[0];
await anchorProgram.rpc.updateParticipationNft(
participationModuloNumber,
{
name,
symbol,
uri,
sellerFeeBasisPoints: sellerFeeBasisPointsNumber,
creators: creatorsList,
isMutable: isMutableBool,
},
{
accounts: {
fairLaunch: fairLaunchKey,
authority: walletKeyPair.publicKey,
//@ts-ignore
metadata: await getMetadata(participationMint),
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
},
},
);
console.log('Update participation metadata.');
});
program
.command('set_participation_nft')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-n, --name <string>', 'name')
.option('-s, --symbol <string>', 'symbol')
.option('-u, --uri <string>', 'uri')
.option(
'-sfbp, --seller-fee-basis-points <string>',
'seller fee basis points',
)
.option(
'-m, --participation-modulo <string>',
'1 if everybody gets it, 4 if only 1 in 4 get it, etc',
)
.option(
'-c, --creators <string>',
'comma separated creator wallets like wallet1,73,true,wallet2,27,false where its wallet, then share, then verified true/false',
)
.option('-nm, --is_not_mutable', 'is not mutable')
.action(async (_, cmd) => {
const {
env,
keypair,
fairLaunch,
name,
symbol,
uri,
sellerFeeBasisPoints,
creators,
isNotMutable,
participationModulo,
} = cmd.opts();
const sellerFeeBasisPointsNumber = parseInt(sellerFeeBasisPoints);
const participationModuloNumber = parseInt(participationModulo);
const creatorsListPre = creators ? creators.split(',') : [];
const creatorsList = [];
for (let i = 0; i < creatorsListPre.length; i += 3) {
creatorsList.push({
address: new anchor.web3.PublicKey(creatorsListPre[i]),
share: parseInt(creatorsListPre[i + 1]),
verified: creatorsListPre[i + 2] == 'true' ? true : false,
});
}
const isMutableBool = isNotMutable ? false : true;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const [participationMint, mintBump] = await getParticipationMint(
//@ts-ignore
fairLaunchObj.authority,
//@ts-ignore
fairLaunchObj.data.uuid,
);
const [participationTokenAccount, tokenBump] = await getParticipationToken(
//@ts-ignore
fairLaunchObj.authority,
//@ts-ignore
fairLaunchObj.data.uuid,
);
await anchorProgram.rpc.setParticipationNft(
mintBump,
tokenBump,
participationModuloNumber,
{
name,
symbol,
uri,
sellerFeeBasisPoints: sellerFeeBasisPointsNumber,
creators: creatorsList,
isMutable: isMutableBool,
},
{
accounts: {
fairLaunch: fairLaunchKey,
authority: walletKeyPair.publicKey,
payer: walletKeyPair.publicKey,
participationMint,
participationTokenAccount,
//@ts-ignore
metadata: await getMetadata(participationMint),
//@ts-ignore
masterEdition: await getMasterEdition(participationMint),
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
},
);
console.log('Set participation metadata.');
});
program
.command('set_token_metadata')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-n, --name <string>', 'name')
.option('-s, --symbol <string>', 'symbol')
.option('-u, --uri <string>', 'uri')
.option(
'-sfbp, --seller-fee-basis-points <string>',
'seller fee basis points',
)
.option(
'-c, --creators <string>',
'comma separated creator wallets like wallet1,73,true,wallet2,27,false where its wallet, then share, then verified true/false',
)
.option('-nm, --is_not_mutable', 'is not mutable')
.action(async (_, cmd) => {
const {
env,
keypair,
fairLaunch,
name,
symbol,
uri,
sellerFeeBasisPoints,
creators,
isNotMutable,
} = cmd.opts();
const sellerFeeBasisPointsNumber = parseInt(sellerFeeBasisPoints);
const creatorsListPre = creators ? creators.split(',') : [];
const creatorsList = [];
for (let i = 0; i < creatorsListPre.length; i += 3) {
creatorsList.push({
address: new anchor.web3.PublicKey(creatorsListPre[i]),
share: parseInt(creatorsListPre[i + 1]),
verified: creatorsListPre[i + 2] == 'true' ? true : false,
});
}
const isMutableBool = isNotMutable ? false : true;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
await anchorProgram.rpc.setTokenMetadata(
{
name,
symbol,
uri,
sellerFeeBasisPoints: sellerFeeBasisPointsNumber,
creators: creatorsList,
isMutable: isMutableBool,
},
{
accounts: {
fairLaunch: fairLaunchKey,
authority: walletKeyPair.publicKey,
payer: walletKeyPair.publicKey,
//@ts-ignore
metadata: await getMetadata(fairLaunchObj.tokenMint),
//@ts-ignore
tokenMint: fairLaunchObj.tokenMint,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
},
);
console.log('Set token metadata.');
});
program
.command('adjust_ticket')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-a, --amount <string>', 'amount')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch, amount } = cmd.opts();
const amountNumber = parseFloat(amount);
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const fairLaunchTicket = (
await getFairLaunchTicket(
//@ts-ignore
fairLaunchObj.tokenMint,
walletKeyPair.publicKey,
)
)[0];
const fairLaunchLotteryBitmap = //@ts-ignore
(await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint))[0];
await adjustTicket({
amountNumber,
fairLaunchObj,
adjuster: walletKeyPair.publicKey,
fairLaunch,
fairLaunchTicket,
fairLaunchLotteryBitmap,
anchorProgram,
payer: walletKeyPair,
adjustMantissa: true,
});
});
program
.command('punch_and_refund_all_outstanding')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option(
'-r, --rpc-url <string>',
'custom rpc url since this is a heavy command',
)
.action(async (_, cmd) => {
const { env, keypair, fairLaunch, rpcUrl } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(
walletKeyPair,
env,
rpcUrl,
);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const fairLaunchLotteryBitmap = (
await getFairLaunchLotteryBitmap(
//@ts-ignore
fairLaunchObj.tokenMint,
)
)[0];
const fairLaunchLotteryBitmapObj =
await anchorProgram.provider.connection.getAccountInfo(
fairLaunchLotteryBitmap,
);
const seqKeys = [];
//@ts-ignore
for (let i = 0; i < fairLaunchObj.numberTicketsSold; i++) {
seqKeys.push(
(
await getFairLaunchTicketSeqLookup(
//@ts-ignore
fairLaunchObj.tokenMint,
new anchor.BN(i),
)
)[0],
);
}
const ticketKeys: any[][] = await Promise.all(
chunks(Array.from(Array(seqKeys.length).keys()), 1000).map(
async allIndexesInSlice => {
let ticketKeys = [];
for (let i = 0; i < allIndexesInSlice.length; i += 100) {
console.log(
'Pulling ticket seqs for slice',
allIndexesInSlice[i],
allIndexesInSlice[i + 100],
);
const slice = allIndexesInSlice
.slice(i, i + 100)
.map(index => seqKeys[index]);
let result;
let tries = 0;
let done = false;
while (tries < 3 && !done) {
try {
result = await getMultipleAccounts(
anchorProgram.provider.connection,
slice.map(s => s.toBase58()),
'recent',
);
done = true;
} catch (e) {
console.log(e);
console.log('Failed, retrying after 10s sleep');
await sleep(10000);
tries += 1;
}
}
ticketKeys = ticketKeys.concat(
result.array.map(
a =>
new anchor.web3.PublicKey(
new Uint8Array(a.data.slice(8, 8 + 32)),
),
),
);
}
return ticketKeys;
},
),
);
const ticketsFlattened = ticketKeys.flat();
const ticketData: { key: anchor.web3.PublicKey; model: any }[][] =
await Promise.all(
chunks(Array.from(Array(ticketsFlattened.length).keys()), 1000).map(
async allIndexesInSlice => {
let states = [];
for (let i = 0; i < allIndexesInSlice.length; i += 100) {
console.log(
'Pulling accounts for slice',
allIndexesInSlice[i],
allIndexesInSlice[i + 100],
);
const slice = allIndexesInSlice
.slice(i, i + 100)
.map(index => ticketsFlattened[index]);
let result;
let tries = 0;
let done = false;
while (tries < 3 && !done) {
try {
result = await getMultipleAccounts(
anchorProgram.provider.connection,
slice.map(s => s.toBase58()),
'recent',
);
done = true;
} catch (e) {
console.log(e);
console.log('Failed, retrying after 10s sleep');
await sleep(10000);
tries += 1;
}
}
states = states.concat(
result.array.map((a, i) => ({
key: new anchor.web3.PublicKey(result.keys[i]),
model: anchorProgram.coder.accounts.decode(
'FairLaunchTicket',
a.data,
),
})),
);
}
return states;
},
),
);
const ticketDataFlat = ticketData.flat();
await Promise.all(
chunks(Array.from(Array(ticketDataFlat.length).keys()), 1000).map(
async allIndexesInSlice => {
for (let i = 0; i < allIndexesInSlice.length; i++) {
const ticket = ticketDataFlat[allIndexesInSlice[i]];
if (!ticket.model.gottenParticipation) {
let tries = 0;
let done = false;
while (tries < 3 && !done) {
try {
const nft = await getParticipationNft({
payer: walletKeyPair,
buyer: ticket.model.buyer,
anchorProgram,
fairLaunchTicket: ticket.key,
fairLaunch,
fairLaunchObj,
fairLaunchTicketObj: ticket.model,
});
done = true;
if (nft) {
console.log(
`Got participation nft and placed token in new account ${nft.toBase58()}.`,
);
}
} catch (e) {
if (tries > 3) {
throw e;
} else {
tries++;
}
console.log(e);
console.log(
'Ticket failed to get participation nft, trying one more time',
);
await sleep(1000);
}
}
} else {
console.log(
'Ticket',
ticket.model.buyer.toBase58(),
'already received participation',
);
}
if (ticket.model.state.unpunched) {
if (
ticket.model.amount.toNumber() <
//@ts-ignore
fairLaunchObj.currentMedian.toNumber()
) {
console.log(
'Refunding ticket for buyer',
allIndexesInSlice[i],
ticket.model.buyer.toBase58(),
);
let tries = 0;
let done = false;
while (tries < 3 && !done) {
try {
await adjustTicket({
amountNumber: 0,
fairLaunchObj,
adjuster: ticket.model.buyer,
fairLaunch,
fairLaunchTicket: ticket.key,
fairLaunchLotteryBitmap,
anchorProgram,
payer: walletKeyPair,
adjustMantissa: true,
});
done = true;
} catch (e) {
if (tries > 3) {
throw e;
} else {
tries++;
}
console.log(e);
console.log(
'Adjusting ticket failed',
ticket.key.toBase58(),
);
await sleep(1000);
}
}
} else {
const myByte =
fairLaunchLotteryBitmapObj.data[
FAIR_LAUNCH_LOTTERY_SIZE +
Math.floor(ticket.model.seq.toNumber() / 8)
];
const positionFromRight = 7 - (ticket.model.seq.toNumber() % 8);
const mask = Math.pow(2, positionFromRight);
const isWinner = myByte & mask;
if (isWinner > 0) {
console.log(
'Punching ticket for buyer',
allIndexesInSlice[i],
ticket.model.buyer.toBase58(),
);
const diff =
ticket.model.amount.toNumber() -
//@ts-ignore
fairLaunchObj.currentMedian.toNumber();
if (diff > 0) {
console.log(
'Refunding first',
diff,
'to buyer',
allIndexesInSlice[i],
'before punching',
);
let tries = 0;
let done = false;
while (tries < 3 && !done) {
try {
await adjustTicket({
//@ts-ignore
amountNumber: fairLaunchObj.currentMedian.toNumber(),
fairLaunchObj,
adjuster: ticket.model.buyer,
fairLaunch,
fairLaunchTicket: ticket.key,
fairLaunchLotteryBitmap,
anchorProgram,
payer: walletKeyPair,
adjustMantissa: false,
});
done = true;
console.log(
'Adjusting ticket succeeded',
ticket.key.toBase58(),
);
} catch (e) {
if (tries > 3) {
throw e;
} else {
tries++;
}
console.log(e);
console.log(
'Adjusting ticket failed',
ticket.key.toBase58(),
);
await sleep(1000);
}
}
}
let tries = 0;
let done = false;
while (tries < 3 && !done) {
try {
const buyerTokenAccount = await punchTicket({
payer: walletKeyPair,
puncher: ticket.model.buyer,
anchorProgram,
fairLaunchTicket: ticket.key,
fairLaunch,
fairLaunchLotteryBitmap,
fairLaunchObj,
fairLaunchTicketObj: ticket.model,
});
done = true;
console.log(
`Punched ticket and placed token in new account ${buyerTokenAccount.toBase58()}.`,
);
} catch (e) {
if (tries > 3) {
throw e;
} else {
tries++;
}
console.log(e);
console.log(
'Ticket failed to punch, trying one more time',
);
await sleep(1000);
}
}
} else {
console.log(
'Buyer ',
allIndexesInSlice[i],
ticket.model.buyer.toBase58(),
'was eligible but lost lottery, refunding',
);
let tries = 0;
let done = false;
while (tries < 3 && !done) {
try {
await adjustTicket({
//@ts-ignore
amountNumber: 0,
fairLaunchObj,
adjuster: ticket.model.buyer,
fairLaunch,
fairLaunchTicket: ticket.key,
fairLaunchLotteryBitmap,
anchorProgram,
payer: walletKeyPair,
adjustMantissa: true,
});
done = true;
console.log(
'Refunding ticket succeeded',
ticket.key.toBase58(),
);
} catch (e) {
if (tries > 3) {
throw e;
} else {
tries++;
}
console.log(e);
console.log(
'Adjusting ticket failed',
ticket.key.toBase58(),
);
await sleep(1000);
}
}
console.log('Refunded.');
}
}
} else if (ticket.model.state.withdrawn) {
console.log(
'Buyer',
allIndexesInSlice[i],
ticket.model.buyer.toBase58(),
'withdrawn already',
);
} else if (ticket.model.state.punched) {
console.log(
'Buyer',
allIndexesInSlice[i],
ticket.model.buyer.toBase58(),
'punched already',
);
}
}
},
),
);
});
async function getParticipationNft({
buyer,
payer,
anchorProgram,
fairLaunchTicket,
fairLaunch,
fairLaunchObj,
fairLaunchTicketObj,
}: {
buyer: anchor.web3.PublicKey;
anchorProgram: anchor.Program;
payer: anchor.web3.Keypair;
fairLaunchTicket: anchor.web3.PublicKey;
fairLaunch: anchor.web3.PublicKey;
fairLaunchObj: any;
fairLaunchTicketObj: any;
}): Promise<anchor.web3.PublicKey | null> {
if (
fairLaunchObj.participationMint &&
fairLaunchTicketObj.seq.toNumber() % fairLaunchObj.participationModulo == 0
) {
console.log(buyer.toBase58(), 'gets participation token.');
const mint = anchor.web3.Keypair.generate();
let signers = [mint];
const tokenAccount = (
await getParticipationToken(
fairLaunchObj.authority,
fairLaunchObj.data.uuid,
)
)[0];
const buyerTokenNft = (await getAtaForMint(mint.publicKey, buyer))[0];
let instructions = [
anchor.web3.SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mint.publicKey,
space: MintLayout.span,
lamports:
await anchorProgram.provider.connection.getMinimumBalanceForRentExemption(
MintLayout.span,
),
programId: TOKEN_PROGRAM_ID,
}),
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
0,
payer.publicKey,
payer.publicKey,
),
createAssociatedTokenAccountInstruction(
buyerTokenNft,
payer.publicKey,
buyer,
mint.publicKey,
),
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mint.publicKey,
buyerTokenNft,
payer.publicKey,
[],
1,
),
];
await anchorProgram.rpc.mintParticipationNft({
accounts: {
fairLaunch,
fairLaunchTicket,
payer: payer.publicKey,
participationMint: fairLaunchObj.participationMint,
participationTokenAccount: tokenAccount,
buyer,
buyerNftTokenAccount: buyerTokenNft,
newMetadata: await getMetadata(mint.publicKey),
newEdition: await getMasterEdition(mint.publicKey),
newMint: mint.publicKey,
newMintAuthority: payer.publicKey,
metadata: await getMetadata(fairLaunchObj.participationMint),
masterEdition: await getMasterEdition(fairLaunchObj.participationMint),
editionMarkPda: await getEditionMarkPda(
fairLaunchObj.participationMint,
fairLaunchTicketObj.seq.toNumber(),
),
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
instructions,
signers,
});
return buyerTokenNft;
} else {
console.log(buyer.toBase58(), 'doesnt get participation token.');
return null;
}
}
async function punchTicket({
puncher,
payer,
anchorProgram,
fairLaunchTicket,
fairLaunch,
fairLaunchLotteryBitmap,
fairLaunchObj,
fairLaunchTicketObj,
}: {
puncher: anchor.web3.PublicKey;
anchorProgram: anchor.Program;
payer: anchor.web3.Keypair;
fairLaunchTicket: anchor.web3.PublicKey;
fairLaunch: anchor.web3.PublicKey;
fairLaunchLotteryBitmap: anchor.web3.PublicKey;
fairLaunchObj: any;
fairLaunchTicketObj: any;
}): Promise<anchor.web3.PublicKey> {
const buyerTokenAccount = (
await getAtaForMint(
//@ts-ignore
fairLaunchObj.tokenMint,
puncher,
)
)[0];
await anchorProgram.rpc.punchTicket({
accounts: {
fairLaunchTicket,
fairLaunch,
fairLaunchLotteryBitmap,
payer: payer.publicKey,
buyerTokenAccount,
//@ts-ignore
tokenMint: fairLaunchObj.tokenMint,
tokenProgram: TOKEN_PROGRAM_ID,
},
options: {
commitment: 'single',
},
//__private: { logAccounts: true },
instructions: [
createAssociatedTokenAccountInstruction(
buyerTokenAccount,
payer.publicKey,
puncher,
//@ts-ignore
fairLaunchObj.tokenMint,
),
],
});
return buyerTokenAccount;
}
program
.command('punch_ticket')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const fairLaunchTicket = (
await getFairLaunchTicket(
//@ts-ignore
fairLaunchObj.tokenMint,
walletKeyPair.publicKey,
)
)[0];
const fairLaunchLotteryBitmap = //@ts-ignore
(await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint))[0];
const ticket = await anchorProgram.account.fairLaunchTicket.fetch(
fairLaunchTicket,
);
const diff =
//@ts-ignore
ticket.amount.toNumber() -
//@ts-ignore
fairLaunchObj.currentMedian.toNumber();
if (diff > 0) {
console.log('Refunding first', diff, 'to buyer before punching');
await adjustTicket({
//@ts-ignore
amountNumber: fairLaunchObj.currentMedian.toNumber(),
fairLaunchObj,
//@ts-ignore
adjuster: ticket.buyer,
fairLaunch,
fairLaunchTicket,
fairLaunchLotteryBitmap,
anchorProgram,
payer: walletKeyPair,
adjustMantissa: false,
});
}
let tries = 0;
let done = false;
//@ts-ignore
if (!ticket.gottenParticipation) {
while (tries < 3 && !done) {
try {
const nft = await getParticipationNft({
buyer: walletKeyPair.publicKey,
payer: walletKeyPair,
anchorProgram,
fairLaunchTicket,
fairLaunch,
fairLaunchObj,
fairLaunchTicketObj: ticket,
});
done = true;
if (nft) {
console.log(
`Punched participation NFT and placed token in new account ${nft.toBase58()}.`,
);
}
} catch (e) {
if (tries > 3) {
throw e;
} else {
tries++;
}
console.log('Ticket failed to punch, trying one more time');
await sleep(1000);
}
}
} else {
console.log('Already got participation');
}
tries = 0;
done = false;
while (tries < 3 && !done) {
try {
const buyerTokenAccount = await punchTicket({
puncher: walletKeyPair.publicKey,
payer: walletKeyPair,
anchorProgram,
fairLaunchTicket,
fairLaunch,
fairLaunchLotteryBitmap,
fairLaunchObj,
fairLaunchTicketObj: ticket,
});
done = true;
console.log(
`Punched ticket and placed token in new account ${buyerTokenAccount.toBase58()}.`,
);
} catch (e) {
if (tries > 3) {
throw e;
} else {
tries++;
}
console.log('Ticket failed to punch, trying one more time');
await sleep(1000);
}
}
});
program
.command('burn_fair_launch_tokens_warning_irreversible')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-n, --number <string>', 'number to burn')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch, number } = cmd.opts();
const actual = parseInt(number);
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const myTokenAccount = (
await getAtaForMint(
//@ts-ignore
fairLaunchObj.tokenMint,
walletKeyPair.publicKey,
)
)[0];
const instructions = [
Token.createBurnInstruction(
TOKEN_PROGRAM_ID,
//@ts-ignore
fairLaunchObj.tokenMint,
myTokenAccount,
walletKeyPair.publicKey,
[],
actual,
),
];
await sendTransactionWithRetryWithKeypair(
anchorProgram.provider.connection,
walletKeyPair,
instructions,
[],
'single',
);
console.log(
`Burned ${actual} tokens in account ${myTokenAccount.toBase58()}.`,
);
});
program
.command('start_phase_three')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const fairLaunchLotteryBitmap = //@ts-ignore
(await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint))[0];
await anchorProgram.rpc.startPhaseThree({
accounts: {
fairLaunch,
fairLaunchLotteryBitmap,
authority: walletKeyPair.publicKey,
//@ts-ignore
tokenMint: fairLaunchObj.tokenMint,
},
});
console.log(`Dang son, phase three.`);
});
program
.command('mint_flp_tokens')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-a, --amount <string>', 'amount')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch, amount } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const amountNumber = parseInt(amount);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const tokenAccount = //@ts-ignore
(await getAtaForMint(fairLaunchObj.tokenMint, walletKeyPair.publicKey))[0];
const exists = await anchorProgram.provider.connection.getAccountInfo(
tokenAccount,
);
const instructions = [];
if (!exists) {
instructions.push(
createAssociatedTokenAccountInstruction(
tokenAccount,
walletKeyPair.publicKey,
walletKeyPair.publicKey,
//@ts-ignore
fairLaunchObj.tokenMint,
),
);
}
await anchorProgram.rpc.mintTokens(new anchor.BN(amountNumber), {
accounts: {
fairLaunch: fairLaunchKey,
authority: walletKeyPair.publicKey,
//@ts-ignore
tokenMint: fairLaunchObj.tokenMint,
tokenProgram: TOKEN_PROGRAM_ID,
tokenAccount,
},
instructions: instructions.length ? instructions : undefined,
});
console.log(`Added ${amountNumber} tokens to ${tokenAccount.toBase58()}`);
});
program
.command('withdraw_funds')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const remainingAccounts = [];
//@ts-ignore
if (fairLaunchObj.treasuryMint) {
remainingAccounts.push({
//@ts-ignore
pubkey: fairLaunchObj.treasuryMint,
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: (
await getAtaForMint(
//@ts-ignore
fairLaunchObj.treasuryMint,
walletKeyPair.publicKey,
)
)[0],
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: TOKEN_PROGRAM_ID,
isWritable: false,
isSigner: false,
});
}
await anchorProgram.rpc.withdrawFunds({
accounts: {
fairLaunch,
// @ts-ignore
treasury: fairLaunchObj.treasury,
authority: walletKeyPair.publicKey,
// @ts-ignore
tokenMint: fairLaunchObj.tokenMint,
systemProgram: anchor.web3.SystemProgram.programId,
},
remainingAccounts,
});
console.log(`Now you rich, give me some.`);
});
program
.command('restart_phase_2')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
await anchorProgram.rpc.restartPhaseTwo({
accounts: {
fairLaunch,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
});
console.log(`Clock restart on phase 2`);
});
program
.command('receive_refund')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const buyerTokenAccount = (
await getAtaForMint(
//@ts-ignore
fairLaunchObj.tokenMint,
walletKeyPair.publicKey,
)
)[0];
const transferAuthority = anchor.web3.Keypair.generate();
const signers = [transferAuthority];
const instructions = [
Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
//@ts-ignore
buyerTokenAccount,
transferAuthority.publicKey,
walletKeyPair.publicKey,
[],
//@ts-ignore
1,
),
];
const remainingAccounts = [];
//@ts-ignore
if (fairLaunchObj.treasuryMint) {
remainingAccounts.push({
//@ts-ignore
pubkey: fairLaunchObj.treasuryMint,
isWritable: true,
isSigner: false,
});
remainingAccounts.push({
pubkey: (
await getAtaForMint(
//@ts-ignore
fairLaunchObj.treasuryMint,
walletKeyPair.publicKey,
)
)[0],
isWritable: true,
isSigner: false,
});
}
const txid = await anchorProgram.rpc.receiveRefund({
accounts: {
fairLaunch,
// @ts-ignore
treasury: fairLaunchObj.treasury,
buyer: walletKeyPair.publicKey,
buyerTokenAccount,
transferAuthority: transferAuthority.publicKey,
// @ts-ignore
tokenMint: fairLaunchObj.tokenMint,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
remainingAccounts,
instructions,
signers,
});
console.log(`You received a refund, traitor. ${txid}`);
});
program
.command('create_fair_launch_lottery')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option(
'-r, --rpc-url <string>',
'custom rpc url since this is a heavy command',
)
.action(async (_, cmd) => {
const { env, keypair, fairLaunch, rpcUrl } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(
walletKeyPair,
env,
rpcUrl,
);
const fairLaunchKey = new anchor.web3.PublicKey(fairLaunch);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const [fairLaunchLotteryBitmap, bump] = await getFairLaunchLotteryBitmap(
//@ts-ignore
fairLaunchObj.tokenMint,
);
const exists = await anchorProgram.provider.connection.getAccountInfo(
fairLaunchLotteryBitmap,
);
if (!exists) {
await anchorProgram.rpc.createFairLaunchLotteryBitmap(bump, {
accounts: {
fairLaunch,
fairLaunchLotteryBitmap,
authority: walletKeyPair.publicKey,
payer: walletKeyPair.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
});
console.log(
`created fair launch lottery bitmap Done: ${fairLaunchLotteryBitmap.toBase58()}.`,
);
} else {
console.log(
`checked fair launch lottery bitmap, exists: ${fairLaunchLotteryBitmap.toBase58()}.`,
);
}
const seqKeys = [];
//@ts-ignore
for (let i = 0; i < fairLaunchObj.numberTicketsSold; i++) {
seqKeys.push(
(
await getFairLaunchTicketSeqLookup(
//@ts-ignore
fairLaunchObj.tokenMint,
new anchor.BN(i),
)
)[0],
);
}
const ticketKeys: anchor.web3.PublicKey[][] = await Promise.all(
chunks(Array.from(Array(seqKeys.length).keys()), 1000).map(
async allIndexesInSlice => {
let ticketKeys = [];
for (let i = 0; i < allIndexesInSlice.length; i += 100) {
console.log(
'Pulling ticket seqs for slice',
allIndexesInSlice[i],
allIndexesInSlice[i + 100],
);
const slice = allIndexesInSlice
.slice(i, i + 100)
.map(index => seqKeys[index]);
const result = await getMultipleAccounts(
anchorProgram.provider.connection,
slice.map(s => s.toBase58()),
'recent',
);
ticketKeys = ticketKeys.concat(
result.array.map(
a =>
new anchor.web3.PublicKey(
new Uint8Array(a.data.slice(8, 8 + 32)),
),
),
);
}
return ticketKeys;
},
),
);
const ticketsFlattened = ticketKeys.flat();
const states: { seq: number; number: anchor.BN; eligible: boolean }[][] =
await Promise.all(
chunks(Array.from(Array(ticketsFlattened.length).keys()), 1000).map(
async allIndexesInSlice => {
let states = [];
for (let i = 0; i < allIndexesInSlice.length; i += 100) {
console.log(
'Pulling states for slice',
allIndexesInSlice[i],
allIndexesInSlice[i + 100],
);
const slice = allIndexesInSlice
.slice(i, i + 100)
.map(index => ticketsFlattened[index]);
const result = await getMultipleAccounts(
anchorProgram.provider.connection,
slice.map(s => s.toBase58()),
'recent',
);
states = states.concat(
result.array.map(a => {
const el = anchorProgram.coder.accounts.decode(
'FairLaunchTicket',
a.data,
);
return {
seq: el.seq.toNumber(),
number: el.amount.toNumber(),
eligible: !!(
el.state.unpunched &&
el.amount.toNumber() >=
//@ts-ignore
fairLaunchObj.currentMedian.toNumber()
),
};
}),
);
}
return states;
},
),
);
const statesFlat = states.flat();
const token = new Token(
anchorProgram.provider.connection,
//@ts-ignore
new anchor.web3.PublicKey(fairLaunchObj.tokenMint),
TOKEN_PROGRAM_ID,
walletKeyPair,
);
const mintInfo = await token.getMintInfo();
let numWinnersRemaining = Math.min(
//@ts-ignore;
fairLaunchObj.data.numberOfTokens.sub(mintInfo.supply),
//@ts-ignore;
statesFlat.filter(s => s.eligible).length,
);
let chosen: { seq: number; eligible: boolean; chosen: boolean }[];
if (numWinnersRemaining >= statesFlat.length) {
console.log('More or equal nfts than winners, everybody wins.');
chosen = statesFlat.map(s => ({ ...s, chosen: true }));
} else {
chosen = statesFlat.map(s => ({ ...s, chosen: false }));
console.log('Doing lottery for', numWinnersRemaining);
while (numWinnersRemaining > 0) {
const rand = Math.floor(Math.random() * (chosen.length));
if (chosen[rand].chosen != true && chosen[rand].eligible) {
chosen[rand].chosen = true;
numWinnersRemaining--;
}
}
}
const sorted = chosen.sort((a, b) => a.seq - b.seq);
console.log('Lottery results', sorted);
await Promise.all(
// each 8 entries is 1 byte, we want to send up 1000 bytes at a time.
// be specific here.
chunks(Array.from(Array(sorted.length).keys()), 8 * 1000).map(
async allIndexesInSlice => {
const bytes = [];
const correspondingArrayOfBits = [];
const startingOffset = allIndexesInSlice[0];
let positionFromRight = 7;
let currByte = 0;
let currByteAsBits = [];
for (let i = 0; i < allIndexesInSlice.length; i++) {
if (chosen[allIndexesInSlice[i]].chosen) {
const mask = Math.pow(2, positionFromRight);
currByte = currByte | mask;
currByteAsBits.push(1);
} else {
currByteAsBits.push(0);
}
positionFromRight--;
if (positionFromRight < 0) {
bytes.push(currByte);
correspondingArrayOfBits.push(currByteAsBits);
currByte = 0;
currByteAsBits = [];
positionFromRight = 7;
}
}
if (positionFromRight != 7) {
// grab the last one if the loop hasnt JUST ended exactly right before on an additional add.
bytes.push(currByte);
correspondingArrayOfBits.push(currByteAsBits);
}
console.log(
'Setting bytes array for',
startingOffset,
'to',
allIndexesInSlice[allIndexesInSlice.length - 1],
'as (with split out by bits for ease of reading)',
bytes.map((e, i) => [e, correspondingArrayOfBits[i]]),
);
await anchorProgram.rpc.updateFairLaunchLotteryBitmap(
startingOffset,
Buffer.from(bytes),
{
accounts: {
fairLaunch,
fairLaunchLotteryBitmap,
authority: walletKeyPair.publicKey,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
},
);
},
),
);
console.log('All done');
});
program
.command('create_missing_sequences')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.action(async (_, cmd) => {
const { env, keypair, fairLaunch } = cmd.opts();
const fairLaunchTicketSeqStart = 8 + 32 + 32 + 8 + 1 + 1;
const fairLaunchTicketState = 8 + 32 + 32 + 8;
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunch,
);
const tickets = await anchorProgram.provider.connection.getProgramAccounts(
FAIR_LAUNCH_PROGRAM_ID,
{
filters: [
{
memcmp: {
offset: 8,
bytes: fairLaunch,
},
},
],
},
);
for (let i = 0; i < tickets.length; i++) {
const accountAndPubkey = tickets[i];
const { account, pubkey } = accountAndPubkey;
const state = account.data[fairLaunchTicketState];
if (state == 0) {
console.log('Missing sequence for ticket', pubkey.toBase58());
const [fairLaunchTicketSeqLookup, seqBump] =
await getFairLaunchTicketSeqLookup(
//@ts-ignore
fairLaunchObj.tokenMint,
new anchor.BN(
account.data.slice(
fairLaunchTicketSeqStart,
fairLaunchTicketSeqStart + 8,
),
undefined,
'le',
),
);
await anchorProgram.rpc.createTicketSeq(seqBump, {
accounts: {
fairLaunchTicketSeqLookup,
fairLaunch,
fairLaunchTicket: pubkey,
payer: walletKeyPair.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
options: {
commitment: 'single',
},
signers: [],
});
console.log('Created...');
}
}
});
program
.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('-f, --fair-launch <string>', 'fair launch id')
.action(async (options, cmd) => {
const { env, fairLaunch, keypair } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunch,
);
let treasuryAmount = 0;
// @ts-ignore
if (fairLaunchObj.treasuryMint) {
const token =
await anchorProgram.provider.connection.getTokenAccountBalance(
// @ts-ignore
fairLaunchObj.treasury,
);
treasuryAmount = token.value.uiAmount;
} else {
treasuryAmount = await anchorProgram.provider.connection.getBalance(
// @ts-ignore
fairLaunchObj.treasury,
);
}
//@ts-ignore
console.log('Token Mint', fairLaunchObj.tokenMint.toBase58());
//@ts-ignore
console.log('Treasury', fairLaunchObj.treasury.toBase58());
//@ts-ignore
console.log('Treasury Mint', fairLaunchObj.treasuryMint?.toBase58());
//@ts-ignore
console.log(
'Participation Mint',
//@ts-ignore
fairLaunchObj.participationMint?.toBase58(),
);
//@ts-ignore
console.log('Authority', fairLaunchObj.authority.toBase58());
//@ts-ignore
console.log('Bump', fairLaunchObj.bump);
//@ts-ignore
console.log('Treasury Bump', fairLaunchObj.treasuryBump);
//@ts-ignore
console.log('Token Mint Bump', fairLaunchObj.tokenMintBump);
//@ts-ignore
console.log('Participation Modulo', fairLaunchObj.participationModulo);
//@ts-ignore
if (fairLaunchObj.data.antiRugSetting) {
console.log('Anti-Rug Settings:');
//@ts-ignore
console.log('Reserve bps', fairLaunchObj.data.antiRugSetting.reserveBp);
//@ts-ignore
console.log(
'Number of tokens remaining in circulation below which you are allowed to retrieve treasury in full:',
//@ts-ignore
fairLaunchObj.data.antiRugSetting.tokenRequirement.toNumber(),
);
console.log(
'Self destruct date - Date at which refunds are allowed (but not required):',
//@ts-ignore
new Date(fairLaunchObj.data.antiRugSetting.selfDestructDate * 1000),
);
} else {
console.log('Anti-Rug Settings: None');
}
console.log(
'Price Range Start ',
//@ts-ignore
fairLaunchObj.data.priceRangeStart.toNumber(),
);
console.log(
'Price Range End ',
//@ts-ignore
fairLaunchObj.data.priceRangeEnd.toNumber(),
);
console.log(
'Tick Size ',
//@ts-ignore
fairLaunchObj.data.tickSize.toNumber(),
);
console.log(
'Fees ',
//@ts-ignore
fairLaunchObj.data.fee.toNumber(),
);
console.log('Current Treasury Holdings', treasuryAmount);
console.log(
'Treasury Snapshot At Peak',
//@ts-ignore
fairLaunchObj.treasurySnapshot?.toNumber(),
);
console.log(
'Phase One Start ',
//@ts-ignore
new Date(fairLaunchObj.data.phaseOneStart.toNumber() * 1000),
);
console.log(
'Phase One End ',
//@ts-ignore
new Date(fairLaunchObj.data.phaseOneEnd.toNumber() * 1000),
);
console.log(
'Phase Two End ',
//@ts-ignore
new Date(fairLaunchObj.data.phaseTwoEnd.toNumber() * 1000),
);
console.log(
'Lottery Period End',
//@ts-ignore
new Date(
//@ts-ignore
(fairLaunchObj.data.phaseTwoEnd.toNumber() +
//@ts-ignore
fairLaunchObj.data.lotteryDuration.toNumber()) *
1000,
),
);
console.log(
'Number of Tokens',
//@ts-ignore
fairLaunchObj.data.numberOfTokens.toNumber(),
);
console.log(
'Number of Tickets Un-Sequenced ',
//@ts-ignore
fairLaunchObj.numberTicketsUnSeqed.toNumber(),
);
console.log(
'Number of Tickets Sold ',
//@ts-ignore
fairLaunchObj.numberTicketsSold.toNumber(),
);
console.log(
'Number of Tickets Dropped ',
//@ts-ignore
fairLaunchObj.numberTicketsDropped.toNumber(),
);
console.log(
'Number of Tickets Punched ',
//@ts-ignore
fairLaunchObj.numberTicketsPunched.toNumber(),
);
console.log(
'Number of Tickets Dropped + Punched',
//@ts-ignore
fairLaunchObj.numberTicketsDropped.toNumber() +
//@ts-ignore
fairLaunchObj.numberTicketsPunched.toNumber(),
);
console.log(
'Number of Tokens Refunded ',
//@ts-ignore
fairLaunchObj.numberTokensBurnedForRefunds.toNumber(),
);
console.log(
'Number of Tokens Preminted ',
//@ts-ignore
fairLaunchObj.numberTokensPreminted.toNumber(),
);
console.log(
'Phase Three Started',
//@ts-ignore
fairLaunchObj.phaseThreeStarted,
);
console.log(
'Current Eligible Holders',
//@ts-ignore
fairLaunchObj.currentEligibleHolders.toNumber(),
);
console.log(
'Current Median',
//@ts-ignore
fairLaunchObj.currentMedian.toNumber(),
);
console.log('Counts at Each Tick');
//@ts-ignore
fairLaunchObj.countsAtEachTick.forEach((c, i) =>
console.log(
//@ts-ignore
fairLaunchObj.data.priceRangeStart.toNumber() +
//@ts-ignore
i * fairLaunchObj.data.tickSize.toNumber(),
':',
c.toNumber(),
),
);
});
program
.command('show_ticket')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.option('-b, --fair-launch-ticket-buyer <string>', 'fair launch ticket buyer')
.action(async (options, cmd) => {
const { env, fairLaunch, keypair, fairLaunchTicketBuyer } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunch,
);
const fairLaunchTicket = (
await getFairLaunchTicket(
//@ts-ignore
fairLaunchObj.tokenMint,
fairLaunchTicketBuyer
? new anchor.web3.PublicKey(fairLaunchTicketBuyer)
: walletKeyPair.publicKey,
)
)[0];
const fairLaunchTicketObj =
await anchorProgram.account.fairLaunchTicket.fetch(fairLaunchTicket);
//@ts-ignore
console.log('Buyer', fairLaunchTicketObj.buyer.toBase58());
//@ts-ignore
console.log('Fair Launch', fairLaunchTicketObj.fairLaunch.toBase58());
//@ts-ignore
console.log('Current Amount', fairLaunchTicketObj.amount.toNumber());
//@ts-ignore
console.log('State', fairLaunchTicketObj.state);
//@ts-ignore
console.log('Bump', fairLaunchTicketObj.bump);
//@ts-ignore
console.log('Sequence', fairLaunchTicketObj.seq.toNumber());
});
program
.command('show_lottery')
.option(
'-e, --env <string>',
'Solana cluster env name',
'devnet', //mainnet-beta, testnet, devnet
)
.option(
'-k, --keypair <path>',
`Solana wallet location`,
'--keypair not provided',
)
.option('-f, --fair-launch <string>', 'fair launch id')
.action(async (options, cmd) => {
const { env, fairLaunch, keypair } = cmd.opts();
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadFairLaunchProgram(walletKeyPair, env);
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunch,
);
const fairLaunchLottery = (
await getFairLaunchLotteryBitmap(
//@ts-ignore
fairLaunchObj.tokenMint,
)
)[0];
const fairLaunchLotteryBitmapObj =
await anchorProgram.provider.connection.getAccountInfo(fairLaunchLottery);
const fairLaunchLotteryBitmapAnchorObj =
await anchorProgram.account.fairLaunchLotteryBitmap.fetch(
fairLaunchLottery,
);
const seqKeys = [];
//@ts-ignore
for (let i = 0; i < fairLaunchObj.numberTicketsSold; i++) {
seqKeys.push(
(
await getFairLaunchTicketSeqLookup(
//@ts-ignore
fairLaunchObj.tokenMint,
new anchor.BN(i),
)
)[0],
);
}
const buyers: { seq: anchor.BN; buyer: anchor.web3.PublicKey }[][] =
await Promise.all(
chunks(Array.from(Array(seqKeys.length).keys()), 1000).map(
async allIndexesInSlice => {
let ticketKeys = [];
for (let i = 0; i < allIndexesInSlice.length; i += 100) {
console.log(
'Pulling ticket seqs for slice',
allIndexesInSlice[i],
allIndexesInSlice[i + 100],
);
const slice = allIndexesInSlice
.slice(i, i + 100)
.map(index => seqKeys[index]);
const result = await getMultipleAccounts(
anchorProgram.provider.connection,
slice.map(s => s.toBase58()),
'recent',
);
ticketKeys = ticketKeys.concat(
result.array.map(a => ({
buyer: new anchor.web3.PublicKey(
new Uint8Array(a.data.slice(8 + 32, 8 + 32 + 32)),
),
seq: new anchor.BN(
a.data.slice(8 + 32 + 32, 8 + 32 + 32 + 8),
undefined,
'le',
),
})),
);
return ticketKeys;
}
},
),
);
const buyersFlattened = buyers
.flat()
.sort((a, b) => a.seq.toNumber() - b.seq.toNumber());
for (let i = 0; i < buyersFlattened.length; i++) {
const buyer = buyersFlattened[i];
const myByte =
fairLaunchLotteryBitmapObj.data[
FAIR_LAUNCH_LOTTERY_SIZE + Math.floor(buyer.seq.toNumber() / 8)
];
const positionFromRight = 7 - (buyer.seq.toNumber() % 8);
const mask = Math.pow(2, positionFromRight);
const isWinner = myByte & mask;
console.log(
'Ticket',
buyer.seq,
buyer.buyer.toBase58(),
isWinner > 0 ? 'won' : 'lost',
);
}
console.log(
'Bit Map ones',
//@ts-ignore
fairLaunchLotteryBitmapAnchorObj.bitmapOnes.toNumber(),
);
});
program.parse(process.argv);