0

This my script for minting NFT:

{-# INLINABLE mkPolicy #-}
mkPolicy :: BuiltinData -> PlutusV2.ScriptContext -> Bool
mkPolicy _ ctx = traceIfFalse "wrong amount minted" checkNFTAmount

  where
    info :: PlutusV2.TxInfo
    info = PlutusV2.scriptContextTxInfo ctx

    -- hasUTxO :: Bool
    -- hasUTxO = any (\i -> PlutusV2.txInInfoOutRef i == mpTxOutRef r) $ PlutusV2.txInfoInputs info

    checkNFTAmount :: Bool
    checkNFTAmount = case Value.flattenValue (PlutusV2.txInfoMint info) of
       [(cs, tn', amt)] -> cs  == ownCurrencySymbol ctx && tn' == PlutusV2.TokenName "" && amt == 1
       _                -> False

{-
    As a Minting Policy
-}

compiledCode :: PlutusTx.CompiledCode (BuiltinData -> BuiltinData -> ())
compiledCode = $$(PlutusTx.compile [|| wrap ||])
  where
    wrap = Scripts.mkUntypedMintingPolicy mkPolicy 

policy :: Scripts.MintingPolicy
policy = PlutusV2.mkMintingPolicyScript compiledCode

script :: PlutusV2.Script
script = PlutusV2.unMintingPolicyScript policy
{-
    As a Short Byte String
-}

scriptSBS :: SBS.ShortByteString
scriptSBS = SBS.toShort . LBS.toStrict $ serialise script

{-
    As a Serialised Script
-}

serialisedScript :: PlutusScript PlutusScriptV2
serialisedScript = PlutusScriptSerialised scriptSBS

writeSerialisedScript :: IO ()
writeSerialisedScript = void $ writeFileTextEnvelope "nft-mint-V2.plutus" Nothing serialisedScript

I'm using Mesh to minting NFT with that script

    const walletAddr = wallet.getPaymentAddress();
    const addressUtxo: UTxO[] = await provider.fetchAddressUTxOs(walletAddr);

    const redeemer: Partial<Action> = {
        tag: "MINT",
        data: {
            alternative: 0,
            fields: [],
        },
    };

    const assetMetadata: AssetMetadata = {
        name: "MyNFT",
        image: "https://picsum.photos/200",
        mediaType: "image/jpg",
        description: "This NFT is minted by me.",
    };

    const asset: Mint = {
        assetName: "MyNFT",
        assetQuantity: "1",
        metadata: assetMetadata,
        label: "721",
        recipient: walletAddr,
    };

    // Mint NFT

    const tx = new Transaction({ initiator: wallet });

    tx.mintAsset(script, asset, redeemer);
    tx.setCollateral([addressUtxo[0]]);

    const unsignedTx = await tx.build();
    const signedTx = await wallet.signTx(unsignedTx, true);
    try {
        const txHash = await wallet.submitTx(signedTx);
        console.log(txHash);
    } catch (e) {
        console.log(e);
    }

Unfortunately, it returned with this error:

transaction submit error ShelleyTxValidationError ShelleyBasedEraBabbage 
(ApplyTxError 
[UtxowFailure 
(UtxoFailure 
(FromAlonzoUtxoFail 
(UtxosFailure 
(ValidationTagMismatch 
(IsValid True) 
(FailedUnexpectedly 
(PlutusFailure \\\"\\\\nThe 2 arg plutus script (PlutusScript PlutusV2 ScriptHash \\\\\\\"77f807bc9403ef0177cc2a9956bfd5628ee649680041ccf48a198fc0\\\\\\\") fails.
\\\\nCekError An error has occurred:  
User error:\\\\nThe machine terminated because of an error, either from a built-in function or from an explicit use of 'error'.
\\\\nThe protocol version is: ProtVer {pvMajor = 7, pvMinor = 0}\\\\nThe redeemer is: Constr 0 []\\\\
nThe second data argument, does not decode to a context

Does anyone faced this error before, what's wrong with my script?

3
  • I think this is to do with the conversion of Data to ScriptContext. How are you compiling and serialising your policy?
    – james
    Commented Feb 8, 2023 at 12:46
  • I updated the compile code above, can you take a look at that @james
    – Daniel Ng
    Commented Feb 8, 2023 at 13:11
  • I think the script is produced correctly. Unfortunately, I cannot help with how it is used to construct a transaction using Mesh. You could start by verifying the script is correct by using cardano-cli.
    – james
    Commented Feb 8, 2023 at 13:40

3 Answers 3

1

As Manu already mentioned it seems like the provider on which wallet.submitTx is based on is passing a Contex of PlutusV1 while your script expects a context of PlutusV2

this is not the fault of mesh; rather than whatever the wallet uses to submit the transaction

You might want to try a different submission method; as an example you could perform a POST request to koios.

NOTE netToDom maps a given KoiosNetwork to the matching sub domain

here is an example on how a transaciton is submitted using the code from koios-pluts

export async function submitTx( tx: Tx, network: KoiosNetwork = "mainnet" ): Promise<Hash32>
{
    const net: KoiosNetwork = tx.body.network === "testnet" ?
        ( network === "preview" ? "preview" : "preprod" ) :
        "mainnet";

    return fetch(`https://${netToDom(net)}.koios.rest/api/v0/submittx`, {
        method: "post",
        headers: {
          'Content-Type': 'application/cbor'
        },
        body: tx.toCbor().toBuffer().buffer
    })
    .then(  res =>  {

        if( !res.ok )
        throw new KoiosError(
            "error submitting '" + tx.hash.toString() + "' transaction; " +
            "endpoint used: " + `https://${netToDom(net)}.koios.rest/api/v0/submittx ` +
            "JSON form of the tranascton: " +
            JSON.stringify(
                tx.toJson(),
                undefined,
                2
            )
        );

        return tx.hash;
    })
}
0

Just an idea, instead of using:

mkPolicy :: BuiltinData -> PlutusV2.ScriptContext -> Bool   

Use:

mkPolicy :: BuiltinData -> PlutusV1.ScriptContext -> Bool   

It looks like MESH is creating a transaction with a valid redeemer but not a valid context. Could be that its creating a context of Plutus V1, witch is different data structure in Plutus V2 where there are more fields.

You will need to change other mentions to PlutusV2 and use PlutusV1 in your code.

-1

Your script seems simple enough, and this is what it would look like in Helios (which is both an on-chain compiler and off-chain tx builder). Maybe try it on helios and then try to debug what's wrong with your mesh implementation.

I've also got a video on minting an nft with Helios here https://www.youtube.com/watch?v=5iPqe-HNIUE

https://github.com/lley154/helios-examples/blob/main/nft/pages/index.tsx

...

  const mintNFT = async (params : any) => {

    const address = params[0];
    const name = params[1];
    const description = params[2];
    const img = params[3];
    const minAdaVal = new Value(BigInt(2000000));  // minimum Ada needed to send an NFT

    // Get wallet UTXOs
    const walletHelper = new WalletHelper(walletAPI);
    const utxos = await walletHelper.pickUtxos(minAdaVal);
 
    // Get change address
    const changeAddr = await walletHelper.changeAddress;

    // Determine the UTXO used for collateral
    const colatUtxo = await walletHelper.pickCollateral();

    // Start building the transaction
    const tx = new Tx();

    // Add the UTXO as inputs
    tx.addInputs(utxos[0]);

    const mintScript =`minting nft
    const TX_ID: ByteArray = #` + utxos[0][0].txId.hex + `
    const txId: TxId = TxId::new(TX_ID)
    const outputId: TxOutputId = TxOutputId::new(txId, ` + utxos[0][0].utxoIdx + `)
    
    func main(ctx: ScriptContext) -> Bool {
        tx: Tx = ctx.tx;
        mph: MintingPolicyHash = ctx.get_current_minting_policy_hash();
    
        assetclass: AssetClass = AssetClass::new(
            mph, 
            "` + name + `".encode_utf8()
        );
        value_minted: Value = tx.minted;
    
        // Validator logic starts
        (value_minted == Value::new(assetclass, 1)).trace("NFT:1 ") &&
        tx.inputs.any((input: TxInput) -> Bool {
                                        (input.output_id == outputId).trace("NFT2: ")
                                        }
        )
    }`
    
    // Compile the helios minting script
    const mintProgram = Program.new(mintScript).compile(optimize);

    // Add the script as a witness to the transaction
    tx.attachScript(mintProgram);

    // Construct the NFT that we will want to send as an output
    const nftTokenName = ByteArrayData.fromString(name).toHex();
    const tokens: [number[], bigint][] = [[hexToBytes(nftTokenName), BigInt(1)]];

    // Create an empty Redeemer because we must always send a Redeemer with
    // a plutus script transaction even if we don't actually use it.
    const mintRedeemer = new ConstrData(0, []);

    // Indicate the minting we want to include as part of this transaction
    tx.mintTokens(
      mintProgram.mintingPolicyHash,
      tokens,
      mintRedeemer
    )

    // Construct the output and include both the minimum Ada as well as the minted NFT
    tx.addOutput(new TxOutput(
      Address.fromBech32(address),
      new Value(minAdaVal.lovelace, new Assets([[mintProgram.mintingPolicyHash, tokens]]))
    ));

    // Add the collateral utxo
    tx.addCollateral(colatUtxo);

    const networkParams = new NetworkParams(
      await fetch(networkParamsUrl)
          .then(response => response.json())
    )

    // Attached the metadata for the minting transaction
    tx.addMetadata(721, {"map": [[mintProgram.mintingPolicyHash.hex, {"map": [[name, 
                                      {
                                        "map": [["name", name], 
                                                ["description", description],
                                                ["image", img]
                                              ]
                                      }
                                  ]]}
                                ]]
                        }
                  );

    console.log("tx before final", tx.dump());

    // Send any change back to the buyer
    await tx.finalize(networkParams, changeAddr);
    console.log("tx after final", tx.dump());

    console.log("Verifying signature...");
    const signatures = await walletAPI.signTx(tx);
    tx.addSignatures(signatures);
    
    console.log("Submitting transaction...");
    const txHash = await walletAPI.submitTx(tx);
    
    console.log("txHash", txHash);
    setTx({ txId: txHash.hex });

Not the answer you're looking for? Browse other questions tagged or ask your own question.