> Feedback: If these docs are stale, missing, or confusing, post sanitized feedback to `https://docs.tempo.xyz/api/feedback` with `source: "mcp"`, a short `message`, and any relevant `toolName`, `relatedResource`, or `client`.
# Mint Stablecoins

Create new tokens by minting them to a specified address. Minting increases the total supply of your stablecoin.

## Steps

::::steps

### Create a Stablecoin

Before you can mint tokens, you need to create a stablecoin. Follow the [Create a Stablecoin](/docs/guide/issuance/create-a-stablecoin) guide to deploy your token.

Once you've created your token, you can proceed to grant the issuer role and mint tokens.

### Grant the Issuer Role

Assign the issuer role to the address that will mint tokens. Minting requires the **`ISSUER_ROLE`**.

<Demo.Container name="Grant Issuer Role" footerVariant="source" src="tempoxyz/examples/tree/main/examples/issuance">
  <Connect stepNumber={1} />

  <AddFunds stepNumber={2} />

  <CreateOrLoadToken stepNumber={3} />

  <GrantTokenRoles stepNumber={4} roles={['issuer']} last />
</Demo.Container>

:::code-group

```tsx twoslash [GrantIssuerRole.tsx]
import React from 'react'
import { Hooks } from 'wagmi/tempo'
import { useQueryClient } from '@tanstack/react-query'

// @noErrors
export function GrantIssuerRole() {
  const queryClient = useQueryClient()
  const tokenAddress = '0x...' // Your token address
  const issuerAddress = '0x...' // Address to grant the issuer role

  const grant = Hooks.token.useGrantRolesSync({ // [!code hl]
    mutation: { // [!code hl]
      onSettled() { // [!code hl]
        queryClient.refetchQueries({ queryKey: ['hasRole'] }) // [!code hl]
      }, // [!code hl]
    }, // [!code hl]
  }) // [!code hl]

  const handleGrantIssuer = async () => { // [!code hl]
    await grant.mutate({ // [!code hl]
      token: tokenAddress, // [!code hl]
      roles: ['issuer'], // [!code hl]
      to: issuerAddress, // [!code hl]
      feeToken: '0x20c0000000000000000000000000000000000001', // [!code hl]
    }) // [!code hl]
  } // [!code hl]

  return (
    <button // [!code hl]
      disabled={grant.isPending} // [!code hl]
      onClick={handleGrantIssuer} // [!code hl]
      type="button" // [!code hl]
    > {/* [!code hl] */}
    {grant.isPending ? 'Granting...' : 'Grant Issuer Role'} {/* [!code hl] */}
    </button> {/* [!code hl] */}
  )
}
```

```tsx twoslash [config.ts] filename="config.ts"
// @noErrors
import { createConfig, http } from 'wagmi'
import { tempo } from 'viem/chains'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  chains: [tempo],
  connectors: [tempoWallet()],
  transports: {
    [tempo.id]: http(),
  },
})
```

:::

### Mint Tokens to a Recipient

Now that the issuer role is granted, you can mint tokens to any address.

<Demo.Container name="Mint Tokens" footerVariant="source" src="tempoxyz/examples/tree/main/examples/issuance">
  <Connect stepNumber={1} />

  <AddFunds stepNumber={2} />

  <CreateOrLoadToken stepNumber={3} />

  <GrantTokenRoles stepNumber={4} roles={['issuer']} />

  <MintToken stepNumber={5} last />
</Demo.Container>

:::code-group

```tsx twoslash [MintToken.tsx]
import React from 'react'
import { Hooks } from 'wagmi/tempo'
import { useConnection } from 'wagmi'
import { parseUnits, pad, stringToHex } from 'viem'
import { useQueryClient } from '@tanstack/react-query'

// @noErrors
export function MintToken() {
  const { address } = useConnection()
  const queryClient = useQueryClient()
  const tokenAddress = '0x...' // Your token address
  const [recipient, setRecipient] = React.useState<string>('')
  const [memo, setMemo] = React.useState<string>('')

  const { data: metadata } = Hooks.token.useGetMetadata({ // [!code hl]
    token: tokenAddress, // [!code hl]
  }) // [!code hl]

  const mint = Hooks.token.useMintSync({ // [!code hl]
    mutation: { // [!code hl]
      onSettled() { // [!code hl]
        queryClient.refetchQueries({ queryKey: ['getBalance'] }) // [!code hl]
      }, // [!code hl]
    }, // [!code hl]
  }) // [!code hl]

  const handleMint = () => { // [!code hl]
    if (!tokenAddress || !recipient || !metadata) return // [!code hl]
    mint.mutate({ // [!code hl]
      amount: parseUnits('100', metadata.decimals), // [!code hl]
      to: recipient as `0x${string}`, // [!code hl]
      token: tokenAddress, // [!code hl]
      memo: memo ? pad(stringToHex(memo), { size: 32 }) : undefined, // [!code hl]
      feeToken: '0x20c0000000000000000000000000000000000001', // [!code hl]
    }) // [!code hl]
  } // [!code hl]

  return (
    <>
      <div>
        <label>Recipient address</label>
        <input
          type="text"
          value={recipient}
          onChange={(e) => setRecipient(e.target.value)}
          placeholder="0x..."
        />
      </div>
      <div>
        <label>Memo (optional)</label>
        <input
          type="text"
          value={memo}
          onChange={(e) => setMemo(e.target.value)}
          placeholder="INV-12345"
        />
      </div>
      <button // [!code hl]
        disabled={!address || mint.isPending} // [!code hl]
        onClick={handleMint} // [!code hl]
        type="button" // [!code hl]
      > {/* [!code hl] */}
      {mint.isPending ? 'Minting...' : 'Mint'} {/* [!code hl] */}
      </button> {/* [!code hl] */}
    </>
  )
}
```

```tsx twoslash [config.ts] filename="config.ts"
// @noErrors
import { createConfig, http } from 'wagmi'
import { tempo } from 'viem/chains'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  chains: [tempo],
  connectors: [tempoWallet()],
  transports: {
    [tempo.id]: http(),
  },
})
```

```solidity [Solidity]
TIP20 token = TIP20(0x20c0000000000000000000000000000000000004);

// Mint 1,000 tokens to the treasury (USD has 6 decimals)
address treasuryAddress = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef;
token.mint(treasuryAddress, 1_000_000_000);

// Mint with a memo for tracking
token.mintWithMemo(
  treasuryAddress,
  1_000_000_000,
  keccak256("Q1_2024_TREASURY_ALLOCATION")
);
```

```rust [Rust]
use alloy::{
    primitives::{address, keccak256, U256},
    providers::ProviderBuilder,
};
use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = ProviderBuilder::new_with_network::<TempoNetwork>()
        .connect(&std::env::var("RPC_URL").expect("No RPC URL set"))
        .await?;

    let token = ITIP20::new( // [!code focus]
        address!("0x20c0000000000000000000000000000000000004"), // [!code focus]
        &provider, // [!code focus]
    ); // [!code focus]

    let treasury_address = address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); // [!code focus]

    // Mint 1,000 tokens to the treasury (USD has 6 decimals)
    token // [!code focus]
        .mint(treasury_address, U256::from(1_000_000_000)) // [!code focus]
        .send() // [!code focus]
        .await? // [!code focus]
        .get_receipt() // [!code focus]
        .await?; // [!code focus]

    // Mint with a memo for tracking
    token // [!code focus]
        .mintWithMemo( // [!code focus]
            treasury_address, // [!code focus]
            U256::from(1_000_000_000), // [!code focus]
            keccak256("Q1_2024_TREASURY_ALLOCATION"), // [!code focus]
        ) // [!code focus]
        .send() // [!code focus]
        .await? // [!code focus]
        .get_receipt() // [!code focus]
        .await?; // [!code focus]

    println!("Tokens minted successfully"); // [!code focus]

    Ok(())
}
```

:::

::::

## Recipes

### Burning Stablecoins

To decrease supply, you can burn tokens from your own balance. Burning requires the **`ISSUER_ROLE`** and sufficient balance in the caller's account.

<Demo.Container name="Burn Your Token" footerVariant="source" src="tempoxyz/examples/tree/main/examples/issuance">
  <Connect stepNumber={1} />

  <AddFunds stepNumber={2} />

  <CreateOrLoadToken stepNumber={3} />

  <GrantTokenRoles stepNumber={4} roles={['issuer']} />

  <MintToken stepNumber={5} />

  <BurnToken stepNumber={6} last />
</Demo.Container>

:::code-group

```tsx twoslash [BurnToken.tsx]
import React from 'react'
import { Hooks } from 'wagmi/tempo'
import { useConnection } from 'wagmi'
import { parseUnits, pad, stringToHex } from 'viem'
import { useQueryClient } from '@tanstack/react-query'

// @noErrors
export function BurnToken() {
  const { address } = useConnection()
  const queryClient = useQueryClient()
  const tokenAddress = '0x...' // Your token address
  const [memo, setMemo] = React.useState<string>('')

  const { data: metadata } = Hooks.token.useGetMetadata({ // [!code hl]
    token: tokenAddress, // [!code hl]
  }) // [!code hl]

  const burn = Hooks.token.useBurnSync({ // [!code hl]
    mutation: { // [!code hl]
      onSettled() { // [!code hl]
        queryClient.refetchQueries({ queryKey: ['getBalance'] }) // [!code hl]
      }, // [!code hl]
    }, // [!code hl]
  }) // [!code hl]

  const handleBurn = () => { // [!code hl]
    if (!tokenAddress || !address || !metadata) return // [!code hl]
    burn.mutate({ // [!code hl]
      amount: parseUnits('100', metadata.decimals), // [!code hl]
      token: tokenAddress, // [!code hl]
      memo: memo ? pad(stringToHex(memo), { size: 32 }) : undefined, // [!code hl]
      feeToken: '0x20c0000000000000000000000000000000000001', // [!code hl]
    }) // [!code hl]
  } // [!code hl]

  return (
    <>
      <div>
        <label>Memo (optional)</label>
        <input
          type="text"
          value={memo}
          onChange={(e) => setMemo(e.target.value)}
          placeholder="INV-12345"
        />
      </div>
      <button // [!code hl]
        disabled={!address || burn.isPending} // [!code hl]
        onClick={handleBurn} // [!code hl]
        type="button" // [!code hl]
      > {/* [!code hl] */}
      {burn.isPending ? 'Burning...' : 'Burn'} {/* [!code hl] */}
      </button> {/* [!code hl] */}
    </>
  )
}
```

```tsx twoslash [config.ts] filename="config.ts"
// @noErrors
import { createConfig, http } from 'wagmi'
import { tempo } from 'viem/chains'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  chains: [tempo],
  connectors: [tempoWallet()],
  transports: {
    [tempo.id]: http(),
  },
})
```

```solidity [Solidity]
TIP20 token = TIP20(0x20c0000000000000000000000000000000000004);

// Burn 100 tokens from your own balance
token.burn(100_000_000);

// Burn with a memo for tracking
token.burnWithMemo(100_000_000, keccak256("REDEMPTION_Q1_2024"));
```

```rust [Rust]
use alloy::{
    primitives::{address, keccak256, U256},
    providers::ProviderBuilder,
};
use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = ProviderBuilder::new_with_network::<TempoNetwork>()
        .connect(&std::env::var("RPC_URL").expect("No RPC URL set"))
        .await?;

    let token = ITIP20::new( // [!code focus]
        address!("0x20c0000000000000000000000000000000000004"), // [!code focus]
        &provider, // [!code focus]
    ); // [!code focus]

    // Burn 100 tokens from your own balance
    token // [!code focus]
        .burn(U256::from(100_000_000)) // [!code focus]
        .send() // [!code focus]
        .await? // [!code focus]
        .get_receipt() // [!code focus]
        .await?; // [!code focus]

    // Burn with a memo for tracking
    token // [!code focus]
        .burnWithMemo( // [!code focus]
            U256::from(100_000_000), // [!code focus]
            keccak256("REDEMPTION_Q1_2024"), // [!code focus]
        ) // [!code focus]
        .send() // [!code focus]
        .await? // [!code focus]
        .get_receipt() // [!code focus]
        .await?; // [!code focus]

    println!("Tokens burned successfully"); // [!code focus]

    Ok(())
}
```

:::

## Best Practices

### Monitor Supply Caps

If your token has a supply cap set, any `mint()` or `mintWithMemo()` call that would exceed the cap will revert with `SupplyCapExceeded()`. You must either:

* Burn tokens to reduce total supply below the cap
* Increase the supply cap (requires `DEFAULT_ADMIN_ROLE`)
* Remove the cap entirely by setting it to `type(uint256).max`

Use [`getMetadata`](https://viem.sh/tempo/actions/token.getMetadata) to check your token's total supply before minting.

### Role Separation

Assign the issuer role to dedicated treasury or minting addresses separate from your admin address. This enhances security by limiting the privileges of any single address.

## Learning Resources

<Cards>
  <Card description="Learn more about TIP-20 tokens on Tempo" to="/docs/protocol/tip20/overview" icon="lucide:coins" title="TIP-20 Tokens" />

  <Card description="Learn about role-based access control and token management" to="/docs/guide/issuance/manage-stablecoin" icon="lucide:shield" title="Manage Your Stablecoin" />
</Cards>
