Mesh LogoMesh

How to Build Multi-Signature Minting Transactions on Cardano

Implement multi-sig NFT minting with the Mesh TypeScript blockchain SDK for Cardano dApp development. Coordinate signatures between browser and server wallets.

Multi-signature (multi-sig) transactions require multiple parties to sign before the transaction can be submitted to the Cardano blockchain. This is essential for NFT minting services where a user pays for minting but the application controls the minting policy in JavaScript Web3 projects.

The multi-sig minting flow:

  1. Client wallet — User's browser wallet provides payment UTxOs
  2. Application wallet — Server-side wallet holds the forging script and signs the mint

This guide shows you how to build a complete multi-sig minting system using the Mesh Cardano SDK for dApp development. View the complete source code on GitHub.

Step 1: How Do You Connect the User's Wallet?

Connect the user's browser wallet and get their address and UTxOs:

import { BrowserWallet } from '@meshsdk/core';
const wallet = await BrowserWallet.enable(walletName);

Or use the React blockchain CardanoWallet component:

import { CardanoWallet, useWallet } from "@meshsdk/react";

export default function Page() {
  const { wallet, connected } = useWallet();

  return <CardanoWallet />
}

Get the client's address and UTXOs:

const recipientAddress = await wallet.getChangeAddress();
const utxos = await wallet.getUtxos();

Select only the UTxOs needed for the minting fee:

const assetMap = new Map<Unit, Quantity>();
assetMap.set("lovelace", mintingFee);
const selectedUtxos = experimentalSelectUtxos(assetMap, utxos, "5000000");

Send selectedUtxos and recipientAddress to your backend API.

Step 2: How Do You Build the Transaction on the Server?

On your backend (Next.js API routes, Express, etc.), initialize the provider and application wallet:

const provider = new BlockfrostProvider(
  '<blockfrost key here>'
);

const meshWallet = new MeshWallet({
  networkId: 0,
  fetcher: provider,
  submitter: provider,
  key: {
    type: 'mnemonic',
    words: yourMnemonic,
  },
});

Define the forging script:

const meshWalletAddress = meshWallet.getChangeAddress();
const forgingScript = ForgeScript.withOneSignature(meshWalletAddress);

Define the NFT metadata (CIP-25 format):

const assetName = 'MeshToken';

const assetMetadata: AssetMetadata = {
  name: 'Mesh Token',
  image: 'ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua',
  mediaType: 'image/jpg',
  description: 'This NFT was minted by Mesh (https://meshjs.dev/).',
};

Build the minting transaction using the user's UTxOs as inputs:

import { MeshTxBuilder, resolveScriptHash, stringToHex } from '@meshsdk/core';

const policyId = resolveScriptHash(forgingScript);
const tokenNameHex = stringToHex(assetName);
const metadata = { [policyId]: { [assetName]: assetMetadata } };

const txBuilder = new MeshTxBuilder({
  fetcher: provider,
});

// Add user's UTXOs as inputs
userUtxos.forEach(utxo => {
  txBuilder.txIn(utxo.input.txHash, utxo.input.outputIndex, utxo.output.amount, utxo.output.address);
});

const unsignedTx = await txBuilder
  .mint("1", policyId, tokenNameHex)
  .mintingScript(forgingScript)
  .txOut(recipientAddress, [{ unit: policyId + tokenNameHex, quantity: "1" }])
  .txOut(bankWalletAddress, [{ unit: "lovelace", quantity: mintingFee }])
  .metadataValue(721, metadata)
  .changeAddress(recipientAddress)
  .complete();

Return the unsigned transaction to the client.

Step 3: How Does the User Sign?

The user signs with partial signing enabled (true) since another signature is required:

const signedTx = await wallet.signTx(unsignedTx, true);

Send the partially signed transaction back to the server.

Step 4: How Does the Application Sign and Submit?

The backend adds its signature and submits:

const meshWalletSignedTx = await meshWallet.signTx(signedTx, true);
const txHash = await meshWallet.submitTx(meshWalletSignedTx);

The transaction is now fully signed and submitted to the Cardano blockchain.

What Security Considerations Apply?

  • Never expose mnemonic phrases in client-side code
  • Validate user UTxOs before building transactions
  • Set reasonable minting fees to cover transaction costs
  • Implement rate limiting to prevent abuse

What Should You Explore Next?

On this page