bmresearch / Solnet.Metaplex

Metaplex Protocol .NET integration library.
MIT License
35 stars 21 forks source link

Metaplex v2 Minting #38

Closed GabrielePicco closed 2 years ago

GabrielePicco commented 2 years ago

I'm trying to replicate a metaplex v2 minting made with the metaplex js cli, here is a sample transaction: https://solscan.io/tx/2qgQRycQLf3mQi9QP1vMidod9XK1rWfhp9eDQss4ME1gFEuHVfWfDxuspsxdFBwf8no5xYj8Ly5H1MqZGntTbE8F?cluster=devnet

With the following solnet code I'm able to recreate it until the MintTo step:

var tokenAMint = new Account();
var fromAccount = new Account();
PublicKey associatedTokenAccount = AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(fromAccount.PublicKey, tokenAMint.PublicKey);

var transaction = new TransactionBuilder()
                .SetRecentBlockHash(blockHash.Result.Value.Blockhash)
                .SetFeePayer(fromAccount)
                .AddInstruction(
                    SystemProgram.CreateAccount(
                        fromAccount,
                        tokenAMint,
                        devnetRpc.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result,
                        TokenProgram.MintAccountDataSize,
                        TokenProgram.ProgramIdKey))
                .AddInstruction(
                    TokenProgram.InitializeMint(
                        tokenAMint,
                        0,
                        fromAccount,
                        fromAccount))
                .AddInstruction(
                    AssociatedTokenAccountProgram.CreateAssociatedTokenAccount(
                        fromAccount,
                        fromAccount.PublicKey,
                        tokenAMint.PublicKey))
                .AddInstruction(
                    TokenProgram.MintTo(
                        tokenAMint.PublicKey,
                        associatedTokenAccount,
                        1,
                        fromAccount.PublicKey))
                .Build(new List<Account>()
                {
                    fromAccount,
                    tokenAMint
                });

Here is the partial transaction: https://solscan.io/tx/3KCg8GmNMXRh1GaHUUnGs5GjeDFfNdEiF5DthdW313TJC4RGw5eXVsQ9mTyJvLkg1PC8SRGSBDfLyfzgMos1sH26?cluster=devnet

I am now stuck on calling the Metaplex NFT Candy Machine V2, I have not found the functionality to do this. Is it possible to do this with the current version of solnet.metaplex? Otherwise anyone has any suggestions, tips to implement it?

BifrostTitan commented 2 years ago

.AddInstruction( TokenProgram.MintTo( tokenAMint.PublicKey, AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(ownerAccount, mintAccount), 1, fromAccount.PublicKey))

AssociatedTokenAccount is undefined use the derive associated token account method to obtain the address properly after creating the token account.

You will most likely have to create your own candy machine by combining the solnet dapp scaffolding example and the metaplex library. I created a basic AI that latches onto the asp.net server app and runs along side it. It cycles through and checks the mint wallet for new transactions then mints the NFT from a collection of arweave links that lead to NFT metadata files. It picks a random metadata file and mints it then sends the NFT to the person that bought it. Removing the NFT from the collection and adding the TX to a back-end ledger to make sure it doesnt mint again etc

GabrielePicco commented 2 years ago

Hi @BifrostTitan and thanks for the answer. I updated my code with initialization, it is actually working till the MintTo, this transaction was created with the above code.

The missing step that I'm not able to reproduce is Instruction 5 from this transaction that was created using the mint_one_token from the candy-machine-v2-cli.ts.

So in my case I already have a candy machine and I just need to interact with the metaplex program to mint one new token from the candy machine (I don't need any server/UI). I'm basically trying to reproduce the mint_one_token function with solnet. Suggestions for implementing this feature?

BifrostTitan commented 2 years ago

Oh I assumed you were building your own centralized candymachine and had trouble minting an NFT. I believe its not implemented since you could build an candy machine scalping bot with the library.

Currently there is no CandyMachineV2 MintNFT program classes implemented for use with SOLNET.Metaplex library, but if you revise the metadata program and vault program classes in the current source you will see how its constructed to interact with other metaplex programs. You can easily craft the classes needed for custom programs and even the candymachine V2.

GabrielePicco commented 2 years ago

I see, is there any plan to add this functionality? I can give it a try looking at the other classes and perhaps I could do a pull request. In our project is needed because we are implementing an in-game minting functionality.

BifrostTitan commented 2 years ago

Don't believe so unless contributed by .NET developer who utilizes metaplex's CM v2 like yourself. The candymachine is more oriented around fast selling NFT drops and is originally coded in typescript. If you are aiming to allow players to purchase NFTs from candymachines inside your game then you will need to create the program classes for it.

You are not forced to use it in order to mint metaplex NFTs though. For my solana based mmorpg built with unreal engine the dedicated server mints metaplex NFTs and sends them using this library. You have all the tools to build your own candy machine as well.

Below is the code needed to mint an immutable signed metaplex NFT with the current library and sends it to whoever purchased/won the NFT. A few variables are undefined since it belongs inside a method but you get the idea.

`

        var wallet = CandyMachineWallet;
        Account ownerAccount = CandyMachineWallet.GetAccount(0);
        Account mintAccount = new Wallet(new Account().PrivateKey.KeyBytes).GetAccount(0);

        byte[] metadataAddress = new byte[32];
        int nonce;
        AddressExtensions.TryFindProgramAddress(new List<byte[]>() { Encoding.UTF8.GetBytes("metadata"), MetadataProgram.ProgramIdKey, mintAccount.PublicKey }, MetadataProgram.ProgramIdKey, out metadataAddress, out nonce );

        var c1 = new Creator(ownerAccount.PublicKey, 100);

        var data = new MetadataParameters()
        {
            name = NFTname,
            symbol = "PROJECTname",
            uri = ARweave_metadata_link,
            creators = new List<Creator>() { c1 },
            sellerFeeBasisPoints = 500
        };
        ulong minBalanceForExemptionMint = rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result;
        var NFTmetadata_instruction = MetadataProgram.CreateMetadataAccount(new PublicKey(metadataAddress), mintAccount.PublicKey, wallet.Account.PublicKey, ownerAccount.PublicKey, ownerAccount.PublicKey, data, true, false);
        PublicKey associatedTokenAccountOwner = new(NFT_Purchaser_Wallet_Address);
        RequestResult<ResponseValue<BlockHash>> blockHash = await rpcClient.GetRecentBlockHashAsync();
        byte[] createAndInitializeMintToTx = new TransactionBuilder().
            SetRecentBlockHash(blockHash.Result.Value.Blockhash).
            SetFeePayer(ownerAccount).
            AddInstruction(SystemProgram.CreateAccount(ownerAccount, mintAccount, minBalanceForExemptionMint, TokenProgram.MintAccountDataSize, TokenProgram.ProgramIdKey)).
            AddInstruction(TokenProgram.InitializeMint(mintAccount.PublicKey, 0, ownerAccount.PublicKey, ownerAccount.PublicKey)).
            AddInstruction(AssociatedTokenAccountProgram.CreateAssociatedTokenAccount(ownerAccount.PublicKey, ownerAccount.PublicKey, mintAccount.PublicKey)).
            AddInstruction(TokenProgram.MintTo(mintAccount.PublicKey, AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(ownerAccount, mintAccount), 1, ownerAccount)).
            AddInstruction(NFTmetadata_instruction).
            AddInstruction(MetadataProgram.SignMetada(new PublicKey(metadataAddress), ownerAccount.PublicKey)).
            AddInstruction(AssociatedTokenAccountProgram.CreateAssociatedTokenAccount(ownerAccount, associatedTokenAccountOwner, mintAccount)).
            AddInstruction(TokenProgram.Transfer(AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(ownerAccount.PublicKey, mintAccount.PublicKey), AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(new PublicKey(NFT_Purchaser_Wallet_Address), mintAccount.PublicKey), 1, ownerAccount)).
            Build(new List<Account> { ownerAccount, mintAccount});

        var tx = await rpcClient.SendTransactionAsync(createAndInitializeMintToTx);`
mariomatic commented 2 years ago

There are plans for a CandyMachine API, but work has not started, at least not as I know. If you are eager , you can look at Solnet.Anchor and try to generate the client for CM with it. If it works, shoot a PR.

GabrielePicco commented 2 years ago

Thank you @BifrostTitan ! In my case the code will be executed client side, so I guess it is safer to interact with the Candy Machine. @mariomatic I will give it a try!

GabrielePicco commented 2 years ago

@mariomatic here is the anchor generated code: https://gist.github.com/GabrielePicco/ab5d1ae31e7cc4be8dea95f0a6d56fdc#file-candymachine-cs It doesn't seems usable to me and it contain several issues when imported in the current solnet.metaplex codebase (BTW: I am new to Solana and Solnet and I may have done something wrong). What is the process you use to add the interface for a program? Do you follow a standard? If I follow the metadata program and vault program classes template (as suggested by @BifrostTitan ) I should be able to create the interface for the CandyMachine, correct?

mariomatic commented 2 years ago

Yes, we follow the standards. But, right now, we are a little behind. If you look at other issues you will see we need to catch up to the new standard. What issues do you get when you try to use the generated code?

GabrielePicco commented 2 years ago

@mariomatic many errors were related to the fact that the generated code had the same name for the class and the namespace. Another set of errors was related to missing classes, the generated Anchor code requires solnet 5.0.3 while solnet.Metaplex currently uses solnet 5.0.1. By updating the dependencies and renaming the namespace I was able to compile. I can tmake a pull request, but the style / standard seems to me very different from the one followed so far in the repository. What standard / guidelines are you following? If you give me some information, I can try to readjust the code. Here a gist with the modified working class: https://gist.github.com/GabrielePicco/e6372bee893b42a492b064137e55c7e5