Mesh LogoMesh

Multi-Signatures Transaction

Multi-signature (multi-sig) transactions require more than one signature before broadcasting. Include two or more signers, such as wallets (Browser Wallet or Mesh Wallet) or Plutus scripts.

Build a multi-sig transaction for minting. Two wallets are involved:

  1. Client wallet (user buying the asset)
  2. Application wallet (holds the forging script)

Connect to the user's CIP30 wallet (BrowserWallet) to request a minting transaction. The backend application wallet (MeshWallet) builds the transaction, and the user signs it. Check out the code here.

Connect wallet (client)

Connect the client's wallet and obtain their address and UTXOs.

Connect with BrowserWallet:

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

Or use the 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();

The change address receives the minted NFTs and change. The client's UTXOs are needed to build the transaction.

Select required UTXOs using experimentalSelectUtxos:

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

experimentalSelectUtxos returns required UTXOs. mintingFee is the minting cost; 5000000 buffers the transaction fee.

Send selectedUtxos and recipientAddress to the backend.

Build transaction (application)

Build the minting transaction.

This guide assumes a backend server (e.g., Vercel API, NestJS, ExpressJS).

Initialize a blockchain provider and Mesh 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 AssetMetadata:

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/).',
};

Create the Mint object:

const asset: Mint = {
  assetName: assetName,
  assetQuantity: '1',
  metadata: assetMetadata,
  label: '721',
  recipient: recipientAddress,
};

Create the transaction. Set inputs, mint asset, send lovelace, set change address, and build:

const tx = new Transaction({ initiator: meshWallet });
tx.setTxInputs(userUtxos);
tx.mintAsset(forgingScript, asset);
tx.sendLovelace(bankWalletAddress, mintingFee);
tx.setChangeAddress(recipientAddress);
const unsignedTx = await tx.build();

Optionally, mask metadata:

originalMetadata = Transaction.readMetadata(unsignedTx);

Store originalMetadata to merge after user signature.

Send the transaction to the client.

Sign transaction (client)

Obtain the client's signature. The wallet prompts for a password. Set partial sign to true.

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

Sign transaction (application)

The backend signs with the application wallet:

const meshWalletSignedTx = await systemWallet.signTx(unsignedTx, true);

Merge masked metadata if applicable:

const signedOriginalTx = Transaction.writeMetadata(
  unsignedTx,
  originalMetadata,
);
const meshWalletSignedTx = await systemWallet.signTx(
  signedOriginalTx,
  true,
);

Submit transaction (application)

Submit the transaction:

const txHash = await wallet.submitTx(signedTx);

You can now build multi-sig transactions.

Check out the code here.