Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NFT launchpad template (contract only) #26

Merged
merged 30 commits into from
Jun 5, 2024
Merged

Add NFT launchpad template (contract only) #26

merged 30 commits into from
Jun 5, 2024

Conversation

0xaptosj
Copy link
Contributor

@0xaptosj 0xaptosj commented Jun 3, 2024

A launchpad for contract deployer to create collections and anyone to mint NFTs.

frontend is a dummy repo, will create separate PR for frontend.

Only file needs review is the contract /templates/nft-template/move/sources/nft_launchpad.move

NOTE
Unlike ez-launch where nft is pre minted, NFTs in this contract are created on the fly when users call mint_nft function. The main reason is on the collection creation UI, we want to support creator only sign 1 tx (excludes the tx that pays arweave), ez-launch expects creator to call pre_mint_nft multiple times after creating the collection, that's something we want to avoid.

THIS IS A TEMPLATE, we should not treat it as a PRODUCT.

@0xaptosj
Copy link
Contributor Author

0xaptosj commented Jun 3, 2024

will remove sh_scripts later after i can use cli in ts

@0xaptosj
Copy link
Contributor Author

0xaptosj commented Jun 3, 2024

Will add more unit test later

@0xaptosj 0xaptosj changed the title Add NFT launchpad template Add NFT launchpad template (contract only) Jun 3, 2024
}
}

fun configure_collection_and_token_properties(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably remove configure_collection_and_token_properties as it's not commonly used, if developer needs it tey can add it back themselves. Thoughts?

Copy link
Contributor

@BriungRi BriungRi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments. Only looked at nft_launchpad.

templates/nft-template/move/sources/nft_launchpad.move Outdated Show resolved Hide resolved
/// No active mint stages
const E_NO_ACTIVE_STAGES: u64 = 7;

const ALLOWLIST_MINT_STAGE_CATEGORY: vector<u8> = b"Allowlist mint stage";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth generalizing this into a vector?

When length(&stages) == 1, there is only one stage
When length(&stages) == 2, there are two stages
etc.

Then, you can also do something like mint_fees that can be "zipped" with stages to represent the mint_fee at each stage.

Can even have a stage with MAX_U64 (or option::none?) mint_price to signify that minting is not allowed at this stage

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also worried that if you change the literal here, it will break existing NFT launches that are trying to look up the stage in the simple map

Copy link
Contributor Author

@0xaptosj 0xaptosj Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also worried that if you change the literal here, it will break existing NFT launches that are trying to look up the stage in the simple map

This contract is meant to be instantiated for each launchpad, not to be used as an sdk, so I don't think updating it will break existing collections.

For deployed contract, changing value of constants is incompatible. (this is wrong)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For deployed contract, changing value of constants is incompatible.

Is this true? I thought you can

Copy link
Member

@GotenJBZ GotenJBZ Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can change constants during upgrade

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i probably changed something else that triggered the incompatible error

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to loop in @JohnChangUK here since this is more likely feedback for better devex with token-minter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally enums would be good to use here. I don't have a strong preference here between using Strings vs numbers for differentiation, and think devex is somewhat similar. If you think using numbers vs Strings is better devex, let me know!

mint_fee_per_nft_by_stages: simple_map::SimpleMap<string::String, u64>,
collection_owner_obj: object::Object<CollectionOwnerObjConfig>,
/// Monotonic increasing ID, Starts from 1
next_nft_id: u64,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't you make use of token::mint_numbered_token?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't find this function, but i used collection::count(collection_obj) instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// If you deploy the module under an object, sender is the object's signer
// If you deploy the moduelr under your own account, sender is your account's signer
fun init_module(sender: &signer) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I like calling sender -> deployer since it will always be the deployer here.

templates/nft-template/move/sources/nft_launchpad.move Outdated Show resolved Hide resolved
templates/nft-template/move/sources/nft_launchpad.move Outdated Show resolved Hide resolved
public_mint_start_time: option::Option<u64>,
public_mint_end_time: option::Option<u64>,
public_mint_limit_per_addr: option::Option<u64>,
public_mint_fee_per_nft: option::Option<u64>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer to import Option so we don't need to option::Option

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i want to keep import consistent, so either import everything to lowest level or everything at high level 🫠

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO I think better to optimize for code readability in these sections over import consistency :)

);

for (i in 0..vector::length(&allowlist)) {
mint_stage::add_to_allowlist(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JohnChangUK I feel iffy about this because this is encouraging vector allowlists which could grow long (and cause a tx to run out of gas). Also, would it be more performant to allow passing in a vector (e.g. pass in a copy of the entire vector and store it)?

Ideally, we support a Merkle version of this at some point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support a Merkle version of this at some point.

I expect token-minter to support that so i can just call it in the template.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowlist is implemented as a smart table in token_minter. (https://github.com/aptos-labs/token-minter/blob/main/token-minter/sources/modules/mint_stage.move#L56)

From an API design standpoint I don't think that coupling collection creation and mint stage is a good idea. Why not create the collection first and separately manage stages / allowlists? You can use the sdk to chain APIs as necessary but the move side should be as modular as possible. The current design doesn't allow creators to add people to allowlist or modify the stages.

Copy link
Contributor

@BriungRi BriungRi Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, I think separating the transactions into:

  1. Create the collection itself
  2. Create each stage

is a good idea because:

  1. It makes each function easier to read
  2. Less likely to run out of gas
  3. Each function has fewer arguments

Plus, someone can always write a higher-level function that combines it

Copy link
Member

@JohnChangUK JohnChangUK Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agreed, collection creation and adding mint stages, coin payments are all done separately as shown here.

We can look into supporting Merkle white lists too, but for now using vectors I think is fine

collection_obj,
stage,
*vector::borrow(&allowlist, i),
*option::borrow(&allowlist_mint_limit_per_addr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if there is a different mint_limit for each address? Should we support this? (I give 5 to Alice, 3 to Bob etc.)

Copy link
Contributor Author

@0xaptosj 0xaptosj Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again for simplicity we don't support it

let collection_owner_obj_signer = &object::generate_signer_for_extending(&collection_owner_config.extend_ref);

let next_nft_id = *option::borrow(&collection::count(collection_obj));
let nft_obj_constructor_ref = &token::create(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BriungRi how do you feel about this part? I want to avoid creator pre mint (because we want to make creator only need to send 1 tx) and forbid users pass in random name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should premint just be another stage that only allowlists the creator?

Looping in @JohnChangUK because I want to discuss whether we should have a mint function that is imported from token-minter. That way, we can potentially have all the mint checks (stages, coin_payment) before the actual mint

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current approach is fine. Later we will add more modules which abstract the minting process, i.e. verify mint stages, payments are made and then mint the relevant token/NFT.

Copy link

@bowenyang007 bowenyang007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an overall spec sheet? One gap I see is that not all configs are mutable after creation. For example, mint stages, fee per stage, allowlist, etc. Usually creators are modifying these along the way. Also more often than not they're first managing the stages before even creating the collection (so that the collection data doesn't leak).

templates/nft-template/move/sources/nft_launchpad.move Outdated Show resolved Hide resolved
Comment on lines +37 to +38
#[event]
struct CreateCollectionEvent has store, drop {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not have a standard in TokenMinter for this @JohnChangUK? Custom event at this level means that the user has to have indexing to get the full benefit of the example contract. That's exactly what we're trying to avoid with the token standards.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a standard one in token minter. No strong opinion, but i usually create custom event just so it's more flexible. I can remove it if you feel strongly against it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No event for collection creation, as collection creation is initially done via the framework. TokenMinter creates the peripherals for the collection only so far.

}

#[event]
struct MintNftEvent has store, drop {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here. Seems like we're creating custom events that should be standard in TokenMinter.

/// Unique per collection
struct CollectionConfig has key {
/// Key is stage, value is mint fee denomination
mint_fee_per_nft_by_stages: simple_map::SimpleMap<string::String, u64>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used that initially, but think it's an overkill here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xaptosj possible to give more specific feedback on why it's overkill? Could be useful feedback for how we can make coin_payment easier to use

name: string::String,
uri: string::String,
pre_mint_amount: u64,
allowlist: option::Option<vector<address>>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not a good idea to output the allowlist here. It could be partial cuz it's only a snapshot at collection creation and it could be way too long.

);

for (i in 0..vector::length(&allowlist)) {
mint_stage::add_to_allowlist(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowlist is implemented as a smart table in token_minter. (https://github.com/aptos-labs/token-minter/blob/main/token-minter/sources/modules/mint_stage.move#L56)

From an API design standpoint I don't think that coupling collection creation and mint stage is a good idea. Why not create the collection first and separately manage stages / allowlists? You can use the sdk to chain APIs as necessary but the move side should be as modular as possible. The current design doesn't allow creators to add people to allowlist or modify the stages.

@0xaptosj
Copy link
Contributor Author

0xaptosj commented Jun 4, 2024

@bowenyang007 this template is meant to be a boilerplate to learn move or modify for their own purpose instead of take it and launch it thing for developers.

With simplicty in mind, I put everything (create collection, set mint stage, set allowlist) in 1 function because it's easy to build UI (user only needs to send 1 tx).

I expect features like modifying allowlist to be added by developers themselves.

Comment on lines 135 to 142
allowlist_start_time: option::Option<u64>,
allowlist_end_time: option::Option<u64>,
allowlist_mint_limit_per_addr: option::Option<u64>,
allowlist_mint_fee_per_nft: option::Option<u64>,
public_mint_start_time: option::Option<u64>,
public_mint_end_time: option::Option<u64>,
public_mint_limit_per_addr: option::Option<u64>,
public_mint_fee_per_nft: option::Option<u64>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have this in a separate function. Too many args being passed in this function I think

Copy link

@bowenyang007 bowenyang007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feedback holds but aligned that we don't need to worry about this for the hackathon. Accept to unblock.

@0xaptosj
Copy link
Contributor Author

0xaptosj commented Jun 5, 2024

The feedback holds but aligned that we don't need to worry about this for the hackathon. Accept to unblock.

Thanks, I started this contract from the idea of simplifying ez_launch_with_stages.move, if we want to have separate endpoints we would go with original ez_launch_with_stages.

@0xaptosj 0xaptosj merged commit fdb366c into main Jun 5, 2024
3 checks passed
@0xaptosj 0xaptosj deleted the j/nft-template branch June 5, 2024 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants