Skip to content

Minting Cashu Token

The process of swapping value for a Cashu token is called "minting". To mint with Coco you first create a canonical mint quote, then prepare a mint operation from that quote. The mint operation amount uses the canonical quote's stored unit.

Before minting, ensure the mint is added and trusted (see Adding a Mint):

ts
// Add and trust the mint first
await coco.mint.addMint('https://minturl.com', { trusted: true });

// Create a quote first
const quote = await coco.quotes.mint.create({
  mintUrl: 'https://minturl.com',
  amount: 21,
  method: 'bolt11',
});

// Prepare an operation from the quote
const pendingMint = await coco.ops.mint.prepare({
  quote,
  amount: 21,
});
ts
const customUnitQuote = await coco.quotes.mint.create({
  mintUrl: 'https://minturl.com',
  amount: { amount: 10, unit: 'usd' },
  method: 'bolt11',
});

const customUnitMint = await coco.ops.mint.prepare({
  quote: customUnitQuote,
  amount: 10,
});

The canonical quote and returned pending mint operation both expose request, the BOLT11 payment request that needs to be paid before minting can happen. When Watchers and Processors are activated (they are by default) Coco will automatically check whether the quote has been paid and redeem it automatically. Use operation events to react to completion.

ts
const quote = await coco.quotes.mint.create({
  mintUrl: 'https://minturl.com',
  amount: 21,
  method: 'bolt11',
});

const pendingMint = await coco.ops.mint.prepare({
  quote,
  amount: 21,
});

console.log('pay this: ', pendingMint.request);
console.log('this is the quote id: ', pendingMint.quoteId);

const completed = new Promise<void>((resolve, reject) => {
  let offFinalized = () => {};
  let offFailed = () => {};
  const cleanup = () => {
    offFinalized();
    offFailed();
  };

  offFinalized = coco.on('mint-op:finalized', (payload) => {
    if (payload.operationId !== pendingMint.id) return;
    cleanup();
    resolve();
  });
  offFailed = coco.on('mint-op:failed', ({ operationId, operation }) => {
    if (operationId !== pendingMint.id) return;
    cleanup();
    reject(new Error(operation.terminalFailure?.reason ?? operation.error ?? 'Mint failed'));
  });
});

await completed;

If you disable the default mint processor and want to finalize manually, listen for the quote update and then call the idempotent operation finalizer.

ts
const ready = new Promise<void>((resolve) => {
  const off = coco.on('mint-quote:updated', (payload) => {
    if (payload.mintUrl !== pendingMint.mintUrl || payload.quoteId !== pendingMint.quoteId) {
      return;
    }
    if (
      'state' in payload.quote &&
      (payload.quote.state === 'PAID' || payload.quote.state === 'ISSUED')
    ) {
      off();
      resolve();
    }
  });
});

await ready;
await coco.ops.mint.finalize(pendingMint.id);

Reusable onchain and BOLT12 mint quotes are created through the same quote API. The quote request is the address, offer, or payment request to fund. Refresh the quote to observe new incoming amount, then prepare one or more mint operations against the same quote ID. Apps that need live updates can listen for mint-quote:updated and filter by { mintUrl, quoteId }.

ts
const quote = await coco.quotes.mint.create({
  mintUrl: 'https://minturl.com',
  method: 'onchain',
  unit: 'sat',
});

console.log('fund this: ', quote.request);

const refreshed = await coco.quotes.mint.refresh({
  mintUrl: 'https://minturl.com',
  quoteId: quote.quoteId,
});

const claimable = refreshed.amountPaid.subtract(refreshed.amountIssued);

if (!claimable.isZero()) {
  const pendingOnchainMint = await coco.ops.mint.prepare({
    quote,
    amount: 5,
  });

  await coco.ops.mint.finalize(pendingOnchainMint.id);
}

BOLT12 uses the same quote-first shape. A fixed amount on a BOLT12 quote is encoded into the reusable offer and constrains each payer payment, but the wallet still chooses how much to mint from the claimable quote balance.

ts
const offerQuote = await coco.quotes.mint.create({
  mintUrl: 'https://minturl.com',
  method: 'bolt12',
  unit: 'sat',
  amount: { amount: 21, unit: 'sat' },
  description: 'Mint 21 sats',
});

console.log('pay this offer:', offerQuote.request);

const refreshedOffer = await coco.quotes.mint.refresh({
  mintUrl: 'https://minturl.com',
  quoteId: offerQuote.quoteId,
});

const pendingOfferMint = await coco.ops.mint.prepare({
  quote: refreshedOffer,
  amount: 10,
});

quoteId identifies the remote quote, not a local mint operation. Store pendingMint.id, pendingOnchainMint.id, or pendingOfferMint.id when you need to resume a specific operation later.

For the full state machine and action reference, see Mint Operations. For multi-unit behavior, see Multi-Unit Support.