-
Notifications
You must be signed in to change notification settings - Fork 17
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
Conversation
will remove sh_scripts later after i can use cli in ts |
Will add more unit test later |
} | ||
} | ||
|
||
fun configure_collection_and_token_properties( |
There was a problem hiding this comment.
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?
There was a problem hiding this 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
.
/// No active mint stages | ||
const E_NO_ACTIVE_STAGES: u64 = 7; | ||
|
||
const ALLOWLIST_MINT_STAGE_CATEGORY: vector<u8> = b"Allowlist mint stage"; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
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>, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 🫠
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- Create the collection itself
- Create each stage
is a good idea because:
- It makes each function easier to read
- Less likely to run out of gas
- Each function has fewer arguments
Plus, someone can always write a higher-level function that combines it
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this 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).
#[event] | ||
struct CreateCollectionEvent has store, drop { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be using the coin_payment module here:
https://github.com/aptos-labs/token-minter/blob/main/token-minter/sources/modules/coin_payment.move
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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>>, |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
@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. |
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>, |
There was a problem hiding this comment.
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
There was a problem hiding this 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.
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. |
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 callpre_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.