Create New Fungible Asset

On Solana, we use PDA as mint authority and update authority to enable permissionless mint. We also use PDA as the signer for creating the token. All token accounts are owned by the launchpad program.


use anchor_spl::{
metadata::{
create_metadata_accounts_v3,
mpl_token_metadata::{programs::MPL_TOKEN_METADATA_ID, types::DataV2},
CreateMetadataAccountsV3, Metadata,
},
token::{Mint, Token},
};
#[derive(Accounts)]
pub struct CreateToken<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// Create mint account
// Same PDA as address of the account and mint/freeze authority
#[account(
init,
seeds = [b"mint"],
bump,
payer = payer,
mint::decimals = 9,
mint::authority = mint_account.key(),
mint::freeze_authority = mint_account.key(),
)]
pub mint_account: Account<'info, Mint>,
/// CHECK: Address validated using constraint
#[account(
mut,
address=Pubkey::find_program_address(
&[
"metadata".as_bytes(),
MPL_TOKEN_METADATA_ID.as_ref(),
mint_account.key().as_ref(),
],
&MPL_TOKEN_METADATA_ID,
).0
)]
pub metadata_account: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub token_metadata_program: Program<'info, Metadata>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
}
pub fn handle_create_token(
ctx: Context<CreateToken>,
token_name: String,
token_symbol: String,
token_uri: String,
) -> Result<()> {
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint_account]]];
create_metadata_accounts_v3(
CpiContext::new_with_signer(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMetadataAccountsV3 {
metadata: ctx.accounts.metadata_account.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
// PDA is mint authority
mint_authority: ctx.accounts.mint_account.to_account_info(),
// PDA is update authority
update_authority: ctx.accounts.mint_account.to_account_info(),
payer: ctx.accounts.payer.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
signer_seeds,
),
DataV2 {
name: token_name,
symbol: token_symbol,
uri: token_uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
},
false,
true,
None,
)?;
Ok(())
}


On Aptos the mint authority is represented by a ref, whoever has access to the ref can mint new fungible assets. We store the mint ref in the fungible asset object itself. In our implementation only the launchpad contract has access to the ref. All fungible asset objects are owned by the launchpad contract.


use aptos_framework::fungible_asset;
use aptos_framework::object;
use aptos_framework::primary_fungible_store;
use aptos_std::math128;
struct FAController has key {
mint_ref: fungible_asset::MintRef,
burn_ref: fungible_asset::BurnRef,
transfer_ref: fungible_asset::TransferRef
}
public entry fun create_fa(
sender: &signer,
max_supply: option::Option<u128>,
name: string::String,
symbol: string::String,
decimals: u8,
icon_uri: string::String,
project_uri: string::String
) acquires Registry {
let fa_obj_constructor_ref = &object::create_sticky_object(@launchpad_addr);
let fa_obj_signer = object::generate_signer(fa_obj_constructor_ref);
let converted_max_supply = if (option::is_some(&max_supply)) {
option::some(
option::extract(&mut max_supply) * math128::pow(10, (decimals as u128))
)
} else {
option::none()
};
primary_fungible_store::create_primary_store_enabled_fungible_asset(
fa_obj_constructor_ref,
converted_max_supply,
name,
symbol,
decimals,
icon_uri,
project_uri
);
let mint_ref = fungible_asset::generate_mint_ref(fa_obj_constructor_ref);
let burn_ref = fungible_asset::generate_burn_ref(fa_obj_constructor_ref);
let transfer_ref = fungible_asset::generate_transfer_ref(fa_obj_constructor_ref);
move_to(&fa_obj_signer, FAController {
mint_ref,
burn_ref,
transfer_ref,
});
}