Metadata for fair launch (#460)

* added deploy cmds

* Too specific

* Add metadata support for fair launch tokens.
This commit is contained in:
Jordan Prince 2021-09-21 21:23:29 -05:00 committed by GitHub
parent ef54f97ca8
commit b48aefdfaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 254 additions and 10 deletions

View File

@ -7,6 +7,7 @@ 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 {
@ -19,6 +20,7 @@ import {
getAtaForMint,
getFairLaunchTicketSeqLookup,
getFairLaunchLotteryBitmap,
getMetadata,
} from './helpers/accounts';
import { chunks, getMultipleAccounts, sleep } from './helpers/various';
import { createAssociatedTokenAccountInstruction } from './helpers/instructions';
@ -825,6 +827,93 @@ async function adjustTicket({
);
}
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 = parseFloat(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(
@ -858,9 +947,8 @@ program
)
)[0];
const fairLaunchLotteryBitmap = ( //@ts-ignore
await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint)
)[0];
const fairLaunchLotteryBitmap = //@ts-ignore
(await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint))[0];
await adjustTicket({
amountNumber,
@ -1189,9 +1277,8 @@ program
)
)[0];
const fairLaunchLotteryBitmap = ( //@ts-ignore
await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint)
)[0];
const fairLaunchLotteryBitmap = //@ts-ignore
(await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint))[0];
const ticket = await anchorProgram.account.fairLaunchTicket.fetch(
fairLaunchTicket,
@ -1314,9 +1401,8 @@ program
const fairLaunchObj = await anchorProgram.account.fairLaunch.fetch(
fairLaunchKey,
);
const fairLaunchLotteryBitmap = ( //@ts-ignore
await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint)
)[0];
const fairLaunchLotteryBitmap = //@ts-ignore
(await getFairLaunchLotteryBitmap(fairLaunchObj.tokenMint))[0];
await anchorProgram.rpc.startPhaseThree({
accounts: {
@ -1831,6 +1917,9 @@ program
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
options: {
commitment: 'single',
},
signers: [],
});
console.log('Created...');

1
rust/Cargo.lock generated
View File

@ -1039,6 +1039,7 @@ dependencies = [
"arrayref",
"spl-associated-token-account",
"spl-token",
"spl-token-metadata",
]
[[package]]

View File

@ -20,3 +20,4 @@ anchor-spl = "0.14.0"
arrayref = "0.3.6"
spl-associated-token-account = { version="1.0.3", features = [ "no-entrypoint" ] }
spl-token = { version="3.1.1", features = [ "no-entrypoint" ] }
spl-token-metadata = { path = "../token-metadata/program", features = [ "no-entrypoint" ] }

View File

@ -19,6 +19,7 @@ use {
},
anchor_spl::token::Mint,
spl_token::{instruction::initialize_account2, state::Account},
spl_token_metadata::instruction::{create_metadata_accounts, update_metadata_accounts},
};
pub const PREFIX: &str = "fair_launch";
@ -959,6 +960,108 @@ pub mod fair_launch {
Ok(())
}
pub fn set_token_metadata<'info>(
ctx: Context<'_, '_, '_, 'info, SetTokenMetadata<'info>>,
data: TokenMetadata,
) -> ProgramResult {
let fair_launch = &ctx.accounts.fair_launch;
let token_mint = fair_launch.token_mint;
let authority_seeds = [PREFIX.as_bytes(), token_mint.as_ref(), &[fair_launch.bump]];
let mut creators: Vec<spl_token_metadata::state::Creator> =
vec![spl_token_metadata::state::Creator {
address: fair_launch.key(),
verified: true,
share: 0,
}];
if let Some(cre) = &data.creators {
for c in cre {
creators.push(spl_token_metadata::state::Creator {
address: c.address,
verified: c.verified,
share: c.share,
});
}
}
let update_infos = vec![
ctx.accounts.token_metadata_program.clone(),
ctx.accounts.token_program.clone(),
ctx.accounts.metadata.clone(),
fair_launch.to_account_info().clone(),
];
if ctx.accounts.metadata.data_is_empty() {
msg!("Creating metadata");
let metadata_infos = vec![
ctx.accounts.metadata.clone(),
ctx.accounts.token_mint.clone(),
ctx.accounts.payer.clone(),
ctx.accounts.token_metadata_program.clone(),
ctx.accounts.token_program.clone(),
ctx.accounts.system_program.clone(),
ctx.accounts.rent.to_account_info().clone(),
fair_launch.to_account_info().clone(),
];
invoke_signed(
&create_metadata_accounts(
*ctx.accounts.token_metadata_program.key,
*ctx.accounts.metadata.key,
*ctx.accounts.token_mint.key,
fair_launch.key(),
*ctx.accounts.payer.key,
ctx.accounts.fair_launch.key(),
data.name,
data.symbol.clone(),
data.uri,
Some(creators),
data.seller_fee_basis_points,
false,
data.is_mutable,
),
metadata_infos.as_slice(),
&[&authority_seeds],
)?;
invoke_signed(
&update_metadata_accounts(
*ctx.accounts.token_metadata_program.key,
*ctx.accounts.metadata.key,
fair_launch.key(),
None,
None,
Some(true),
),
update_infos.as_slice(),
&[&authority_seeds],
)?;
} else {
msg!("Updating metadata");
invoke_signed(
&update_metadata_accounts(
*ctx.accounts.token_metadata_program.key,
*ctx.accounts.metadata.key,
fair_launch.key(),
None,
Some(spl_token_metadata::state::Data {
name: data.name,
symbol: data.symbol,
uri: data.uri,
creators: Some(creators),
seller_fee_basis_points: data.seller_fee_basis_points,
}),
None,
),
update_infos.as_slice(),
&[&authority_seeds],
)?;
}
Ok(())
}
}
#[derive(Accounts)]
#[instruction(bump: u8, treasury_bump: u8, token_mint_bump: u8, data: FairLaunchData)]
@ -1167,6 +1270,30 @@ pub struct ReceiveRefund<'info> {
// [Writable/optional] buyer payment token account (must be ata)
}
#[derive(Accounts)]
pub struct SetTokenMetadata<'info> {
#[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump, has_one=authority, has_one=token_mint)]
fair_launch: ProgramAccount<'info, FairLaunch>,
#[account(mut, signer)]
authority: AccountInfo<'info>,
#[account(mut, signer)]
payer: AccountInfo<'info>,
// With the following accounts we aren't using anchor macros because they are CPI'd
// through to token-metadata which will do all the validations we need on them.
#[account(mut)]
metadata: AccountInfo<'info>,
#[account(mut)]
token_mint: AccountInfo<'info>,
#[account(address = spl_token_metadata::id())]
token_metadata_program: AccountInfo<'info>,
#[account(address = spl_token::id())]
token_program: AccountInfo<'info>,
#[account(address = system_program::ID)]
system_program: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
clock: Sysvar<'info, Clock>,
}
pub const FAIR_LAUNCH_LOTTERY_SIZE: usize = 8 + // discriminator
32 + // fair launch
1 + // bump
@ -1222,6 +1349,31 @@ pub const FAIR_LAUNCH_TICKET_SEQ_SIZE: usize = 8 + //discriminator
1 + // bump
50; // padding;
// Note both TokenMetadata/Creator copied over from token metadata due to anchor needing them
// in file to put into IDL
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
pub struct Creator {
pub address: Pubkey,
pub verified: bool,
// In percentages, NOT basis points ;) Watch out!
pub share: u8,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
pub struct TokenMetadata {
/// The name of the asset
pub name: String,
/// The symbol for the asset
pub symbol: String,
/// URI pointing to JSON representing the asset
pub uri: String,
/// Royalty basis points that goes to creators in secondary sales (0-10000)
pub seller_fee_basis_points: u16,
/// Array of creators, optional
pub creators: Option<Vec<Creator>>,
pub is_mutable: bool,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
pub struct AntiRugSetting {
/// basis points kept in the treasury until conditions are met
@ -1277,6 +1429,7 @@ pub struct FairLaunch {
pub current_eligible_holders: u64,
pub current_median: u64,
pub counts_at_each_tick: Vec<u64>,
// Todo add participation fields in the future for participation NFTs
}
#[account]

File diff suppressed because one or more lines are too long