Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"eslint.nodePath": ".yarn/sdks",
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".yarn/sdks/prettier/index.js"
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
}
9 changes: 9 additions & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
token_metadata = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

[programs.devnet]
quarry_merge_mine = "QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"
Expand All @@ -26,6 +27,7 @@ quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
token_metadata = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

[programs.testnet]
quarry_merge_mine = "QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"
Expand All @@ -34,6 +36,7 @@ quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
token_metadata = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

[programs.localnet]
quarry_merge_mine = "QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"
Expand All @@ -42,3 +45,9 @@ quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
token_metadata = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

[test.validator]
[[test.genesis]]
address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
program = "target/deploy/token_metadata.so"
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
"lint": "eslint . --cache",
"lint:ci": "eslint . --max-warnings=0",
"test:e2e": "anchor test --skip-build 'tests/**/*.ts'",
"test:e2e:mintwrapper": "anchor test --skip-build 'tests/**/*mintWrapper*.ts'",
"docs:generate": "typedoc --excludePrivate --includeVersion --out site/ts/ src/index.ts",
"prepare": "husky install"
},
"devDependencies": {
"@metaplex-foundation/mpl-token-metadata": "2.8.6",
"@project-serum/anchor": "^0.25.0",
"@rushstack/eslint-patch": "^1.2.0",
"@saberhq/anchor-contrib": "^1.14.8",
Expand Down
2 changes: 2 additions & 0 deletions programs/quarry-mint-wrapper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ anchor-lang = ">=0.22, <=0.25"
anchor-spl = ">=0.22, <=0.25"
solana-security-txt = "1.0.1"
vipers = "^2.0"
solana-program = "1.9.16"
borsh = ">=0.9.0,<1.0.0"
12 changes: 12 additions & 0 deletions programs/quarry-mint-wrapper/src/account_validators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ impl<'info> Validate<'info> for PerformMint<'info> {
}
}

impl<'info> Validate<'info> for SetMetaplexUpdateAuthority<'info> {
fn validate(&self) -> Result<()> {
invariant!(
self.mint_wrapper.to_account_info().is_writable,
Unauthorized
);

assert_keys_eq!(self.token_mint, self.mint_wrapper.token_mint);
Ok(())
}
}

/// --------------------------------
/// Account Structs
/// --------------------------------
Expand Down
2 changes: 2 additions & 0 deletions programs/quarry-mint-wrapper/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod new_minter;
pub mod new_wrapper;
pub mod set_metaplex_update_authority;
pub use set_metaplex_update_authority::*;
pub use new_minter::*;
pub use new_wrapper::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
use crate::*;

use borsh::BorshSerialize;
use borsh::BorshDeserialize;
use solana_program::pubkey;

static METADATA_PROGRAM_ID: Pubkey = pubkey!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");

pub fn handler(ctx: Context<SetMetaplexUpdateAuthority>) -> Result<()> {

let mint_wrapper = &ctx.accounts.mint_wrapper;

check_mpl_metadata_account_address(&ctx.accounts.metadata_info.key(), &ctx.accounts.token_mint.key())?;

// Check if we should udpate or create
if ctx.accounts.metadata_info.data_is_empty() {

// Metadata account does not yet exists so we need to create it first
let new_metadata_instruction = create_metadata_accounts_v3(
*ctx.accounts.metadata_program.key,
*ctx.accounts.metadata_info.key,
ctx.accounts.token_mint.key(),
ctx.accounts.mint_wrapper.key(),
ctx.accounts.minter_authority.key(), /* payer */
ctx.accounts.mint_wrapper.key(), /* update authority */
String::from(""),
String::from(""),
String::from(""),
);

let seeds = gen_wrapper_signer_seeds!(mint_wrapper);
let proxy_signer = &[&seeds[..]];

solana_program::program::invoke_signed(
&new_metadata_instruction,
&[
ctx.accounts.metadata_info.clone(),
ctx.accounts.token_mint.to_account_info(),
ctx.accounts.mint_wrapper.to_account_info(),
ctx.accounts.minter_authority.to_account_info(),
ctx.accounts.mint_wrapper.to_account_info(), /* update authority */
ctx.accounts.system_program.to_account_info(),
],
proxy_signer,
)?;

}

let update_metadata_accounts_instruction = update_metadata_accounts_v2(
*ctx.accounts.metadata_program.key,
*ctx.accounts.metadata_info.key,
ctx.accounts.mint_wrapper.key(),
Some(ctx.accounts.new_update_authority.key()),
Some(DataV2 {
name: String::from(""),
symbol: String::from(""),
uri: String::from(""),
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
}),
None,
Some(true),
);

let seeds = gen_wrapper_signer_seeds!(mint_wrapper);
let proxy_signer = &[&seeds[..]];

solana_program::program::invoke_signed(
&update_metadata_accounts_instruction,
&[ctx.accounts.metadata_info.to_account_info(), ctx.accounts.mint_wrapper.to_account_info()],
proxy_signer,
)?;

Ok(())
}


pub mod pda {
use {super::METADATA_PROGRAM_ID, solana_program::pubkey::Pubkey};
const PREFIX: &str = "metadata";
/// Helper to find a metadata account address
pub fn find_metadata_account(mint: &Pubkey) -> (Pubkey, u8) {
let id = METADATA_PROGRAM_ID;
Pubkey::find_program_address(&[PREFIX.as_bytes(), id.as_ref(), mint.as_ref()], &id)
}
}

/// Check mpl metadata account address
fn check_mpl_metadata_account_address(
metadata_address: &Pubkey,
mint: &Pubkey,
) -> Result<()> {
let (metadata_account_pubkey, _) = pda::find_metadata_account(mint);

if metadata_account_pubkey != *metadata_address {
Err(super::super::ErrorCode::Unauthorized.into())
} else {
Ok(())
}
}

#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
struct DataV2 {
/// 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,
/// UNUSED Array of creators, optional
pub creators: Option<u8>,
/// UNUSED Collection
pub collection: Option<u8>,
/// UNUSED Uses
pub uses: Option<u8>,
}

#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
struct CreateMetadataAccountArgsV3 {
/// Note that unique metadatas are disabled for now.
pub data: DataV2,
/// Whether you want your metadata to be updateable in the future.
pub is_mutable: bool,
/// UNUSED If this is a collection parent NFT.
pub collection_details: Option<u8>,
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn create_metadata_accounts_v3(
program_id: Pubkey,
metadata_account: Pubkey,
mint: Pubkey,
mint_authority: Pubkey,
payer: Pubkey,
update_authority: Pubkey,
name: String,
symbol: String,
uri: String,
) -> solana_program::instruction::Instruction {
let mut data = vec![33]; // CreateMetadataAccountV3
data.append(
&mut borsh::to_vec(&CreateMetadataAccountArgsV3 {
data: DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
},
is_mutable: true,
collection_details: None,
})
.unwrap(),
);
solana_program::instruction::Instruction {
program_id,
accounts: vec![
AccountMeta::new(metadata_account, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new_readonly(mint_authority, true),
AccountMeta::new(payer, true),
AccountMeta::new_readonly(update_authority, true),
AccountMeta::new_readonly(solana_program::system_program::ID, false),
],
data,
}
}

#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
struct UpdateMetadataAccountArgsV2 {
pub data: Option<DataV2>,
pub update_authority: Option<Pubkey>,
pub primary_sale_happened: Option<bool>,
pub is_mutable: Option<bool>,
}

fn update_metadata_accounts_v2(
program_id: Pubkey,
metadata_account: Pubkey,
update_authority: Pubkey,
new_update_authority: Option<Pubkey>,
metadata: Option<DataV2>,
primary_sale_happened: Option<bool>,
is_mutable: Option<bool>,
) -> solana_program::instruction::Instruction {
let mut data = vec![15]; // UpdateMetadataAccountV2
data.append(
&mut borsh::to_vec(&UpdateMetadataAccountArgsV2 {
data: metadata,
update_authority: new_update_authority,
primary_sale_happened,
is_mutable,
})
.unwrap(),
);
solana_program::instruction::Instruction {
program_id,
accounts: vec![
AccountMeta::new(metadata_account, false),
AccountMeta::new_readonly(update_authority, true),
],
data,
}
}


#[derive(Accounts, Clone)]
pub struct SetMetaplexUpdateAuthority<'info> {
/// [MintWrapper].
#[account(mut)]
pub mint_wrapper: Account<'info, MintWrapper>,

/// [Minter]'s authority.
pub minter_authority: Signer<'info>,

/// Token [Mint].
#[account(mut)]
pub token_mint: Account<'info, Mint>,

/// CHECK: OK
#[account(address = METADATA_PROGRAM_ID)]
#[account(executable)]
pub metadata_program: AccountInfo<'info>,

/// CHECK: OK
#[account(mut)]
pub metadata_info: AccountInfo<'info>,

/// CHECK: OK
#[account(mut)]
pub new_update_authority: AccountInfo<'info>,

pub system_program: Program<'info, System>,

/// CHECK: OK
#[account(address = solana_program::sysvar::instructions::ID)]
pub sysvar_instructions: AccountInfo<'info>,
}
7 changes: 7 additions & 0 deletions programs/quarry-mint-wrapper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ pub mod quarry_mint_wrapper {
});
Ok(())
}

/// Creates metadata for the mint token
#[access_control(ctx.accounts.validate())]
pub fn set_metaplex_update_authority(ctx: Context<SetMetaplexUpdateAuthority>) -> Result<()> {
instructions::set_metaplex_update_authority::handler(ctx)
}

}

// --------------------------------
Expand Down
8 changes: 2 additions & 6 deletions scripts/generate-idl-types.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ generate_declaration_file() {

prog="$(basename $PROGRAM_SO .json)"
OUT_PATH="$OUT_DIR/$prog.ts"
if [ ! $(which gsed) ]; then
PREFIX=$(echo $prog | sed -E 's/(^|_)([a-z])/\U\2/g')
else
PREFIX=$(echo $prog | gsed -E 's/(^|_)([a-z])/\U\2/g')
fi
PREFIX=$(echo $prog | gsed -E 's/(^|_)([a-z])/\U\2/g')
typename="${PREFIX}IDL"
rawName="${PREFIX}JSON"

Expand Down Expand Up @@ -57,4 +53,4 @@ generate_sdk_idls() {
fi
}

generate_sdk_idls ./src/idls 'artifacts/idl/*.json'
generate_sdk_idls ./src/idls 'artifacts/idl/*.json'
3 changes: 2 additions & 1 deletion scripts/parse-idls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
rm -fr artifacts/idl/
mkdir -p artifacts/idl/

echo "Anchor version $(anchor --version)"
for PROGRAM in $(find programs/ -maxdepth 3 -name lib.rs); do
PROGRAM_NAME=$(dirname $PROGRAM | xargs dirname | xargs basename | tr '-' '_')
echo "Parsing IDL for $PROGRAM_NAME"
anchor idl parse --file $PROGRAM --out artifacts/idl/$PROGRAM_NAME.json --out-ts artifacts/idl/$PROGRAM_NAME.ts || {
echo "Could not parse IDL"
exit 1
}
done
done
Loading