Skip to content

Manage Your Stablecoin

Configure your stablecoin's permissions, supply limits, and compliance policies after deployment. This guide covers granting roles to manage token operations, setting supply caps, configuring transfer policies, and controlling token transfers through pause/unpause functionality.

TIP-20 tokens use a role-based access control system that allows you to delegate different administrative functions to different addresses. For detailed information about the role system, see the TIP-20 specification.

Steps

In this guide, we'll walk through how to assign and check the issuer role, but the process is identical for other roles like pause, unpause, burnBlocked, and defaultAdmin.

Setup

Before you can manage roles on your stablecoin, you need to create one. Follow the Create a Stablecoin guide to deploy your token.

Once you've created your token, you can proceed to grant roles to specific addresses.

Grant Roles to an Address

Assign roles to specific addresses to delegate token management capabilities.

Grant Roles to an Address

demo
1
Create an account, or use an existing one.
2
Add testnet funds to your account.
3
Create & deploy a token to testnet.
4
Grant issuer role on token.
pnpx gitpick tempoxyz/tempo-ts/tree/main/examples/issuance
GrantRoles.tsx
import React from 'react'
import { Hooks } from 'tempo.ts/wagmi'
import { useQueryClient } from '@tanstack/react-query'
 
export function GrantRoles() {
  const queryClient = useQueryClient()
  const tokenAddress = '0x...' // Your token address
  const treasuryAddress = '0x...' // Address to grant the issuer role
 
  const grant = Hooks.token.useGrantRolesSync({}) 
 
  const handleGrantIssuer = async () => { 
    await grant.mutate({ 
      token: tokenAddress, 
      roles: ['issuer'], 
      to: treasuryAddress, 
    }) 
  } 
 
  return (
    <button
      disabled={grant.isPending}
      onClick={handleGrantIssuer}
      type="button"
    > 
      {grant.isPending ? 'Granting...' : 'Grant Issuer Role'}
    </button> {}
  )
}

Check if an Address Has a Role

Use hasRole to verify whether an address has been granted a specific role.

GrantRoles.tsx
import React from 'react'
import { Hooks } from 'tempo.ts/wagmi'
import { useQueryClient } from '@tanstack/react-query'
 
export function GrantRoles() {
  const queryClient = useQueryClient()
  const tokenAddress = '0x...' // Your token address
  const treasuryAddress = '0x...' // Address to grant the issuer role
 
  // Grant the issuer role
  const grant = Hooks.token.useGrantRolesSync({
    mutation: { 
      onSettled() { 
        queryClient.refetchQueries({ queryKey: ['hasRole'] }) 
      }, 
    }, 
  })
 
  const handleGrantIssuer = async () => {
    await grant.mutate({
      token: tokenAddress,
      roles: ['issuer'],
      to: treasuryAddress,
    })
  }
 
  const { data: hasIssuerRole } = Hooks.token.useHasRole({ 
    account: treasuryAddress, 
    token: tokenAddress, 
    role: 'issuer', 
  }) 
 
  return (
    <div>
      <button
        disabled={grant.isPending}
        onClick={handleGrantIssuer}
        type="button"
      >
        {grant.isPending ? 'Granting...' : 'Grant Issuer Role'}
      </button>
 
      {hasIssuerRole !== undefined && ( 
        <div> 
          Treasury {hasIssuerRole ? 'has' : 'does not have'} the issuer role 
        </div> {}
      )}
    </div>
  )
}

Revoke the Issuer Role

Revoke roles from addresses when you need to remove their permissions.

Revoke Issuer Role

demo
1
Create an account, or use an existing one.
2
Add testnet funds to your account.
3
Create & deploy a token to testnet.
4
Grant issuer role on token.
5
Revoke issuer role on token.
pnpx gitpick tempoxyz/tempo-ts/tree/main/examples/issuance
RevokeRoles.tsx
import React from 'react'
import { Hooks } from 'tempo.ts/wagmi'
import { useQueryClient } from '@tanstack/react-query'
 
export function RevokeRoles() {
  const queryClient = useQueryClient()
  const tokenAddress = '0x...' // Your token address
  const treasuryAddress = '0x...' // Address to grant/revoke the issuer role
 
  // Grant the issuer role
  const grant = Hooks.token.useGrantRolesSync({
    mutation: {
      onSettled() {
        queryClient.refetchQueries({ queryKey: ['hasRole'] })
      },
    },
  })
 
  const handleGrantIssuer = async () => {
    await grant.mutate({
      token: tokenAddress,
      roles: ['issuer'],
      to: treasuryAddress,
    })
  }
 
  // Check if the treasury has the issuer role
  const { data: hasIssuerRole } = Hooks.token.useHasRole({
    account: treasuryAddress,
    token: tokenAddress,
    role: 'issuer',
  })
 
  // Revoke the issuer role 
  const revoke = Hooks.token.useRevokeRolesSync({ 
    mutation: { 
      onSettled() { 
        queryClient.refetchQueries({ queryKey: ['hasRole'] }) 
      }, 
    }, 
  }) 
 
  const handleRevokeIssuer = async () => { 
    await revoke.mutate({ 
      token: tokenAddress, 
      roles: ['issuer'], 
      from: treasuryAddress, 
    }) 
  } 
 
  return (
    <div>
      <button
        disabled={grant.isPending}
        onClick={handleGrantIssuer}
        type="button"
      >
        {grant.isPending ? 'Granting...' : 'Grant Issuer Role'}
      </button>
 
      <button
        disabled={revoke.isPending || !hasIssuerRole}
        onClick={handleRevokeIssuer}
        type="button"
      > 
        {revoke.isPending ? 'Revoking...' : 'Revoke Issuer Role'}
      </button> 
 
      {hasIssuerRole !== undefined && (
        <div>
          Treasury {hasIssuerRole ? 'has' : 'does not have'} the issuer role
        </div>
      )}
    </div>
  )
}

Recipes

Set Supply Cap

Limit the maximum total supply of your token. Setting supply caps requires the DEFAULT_ADMIN_ROLE. The new cap cannot be less than the current total supply.

Set Supply Cap

demo
1
Create an account, or use an existing one.
2
Add testnet funds to your account.
3
Create & deploy a token to testnet.
4
Set supply cap to 1,000 tokens.
pnpx gitpick tempoxyz/tempo-ts/tree/main/examples/issuance
SetSupplyCap.tsx
import React from 'react'
import { Hooks } from 'tempo.ts/wagmi'
import { parseUnits } from 'viem'
 
export function SetSupplyCap() {
  const tokenAddress = '0x...' // Your token address
 
  const { data: metadata, refetch: refetchMetadata } =
    Hooks.token.useGetMetadata({ token: tokenAddress }) 
 
  const setSupplyCap = Hooks.token.useSetSupplyCapSync({ 
    mutation: { onSettled: () => refetchMetadata() }, 
  }) 
 
  const handleSetSupplyCap = () => { 
    setSupplyCap.mutate({ 
      token: tokenAddress, 
      supplyCap: parseUnits('1000', metadata?.decimals || 6), 
    }) 
  } 
 
  return (
    <button
      disabled={setSupplyCap.isPending}
      onClick={handleSetSupplyCap}
      type="button"
    > 
      {setSupplyCap.isPending ? 'Setting...' : 'Set Cap'}
    </button> {}
  )
}

Configure Transfer Policies

Control who can send and receive your stablecoin for compliance and regulatory requirements. Setting transfer policies requires the DEFAULT_ADMIN_ROLE.

Transfer policies can be:

  • Always allow: Anyone can send/receive (default)
  • Always reject: Nobody can send/receive
  • Whitelist: Only authorized addresses can send/receive
  • Blacklist: Blocked addresses cannot send/receive

Learn more about configuring transfer policies in the TIP-403 specification.

Create and Link Transfer Policy

demo
1
Create an account, or use an existing one.
2
Add testnet funds to your account.
3
Create & deploy a token to testnet.
4
Create a transfer policy.
5
Link the policy to your token.
pnpx gitpick tempoxyz/tempo-ts/tree/main/examples/issuance
CreateTokenPolicy.tsx
import React from 'react'
import { Hooks } from 'tempo.ts/wagmi'
 
export function CreateTokenPolicy() {
  const tokenAddress = '0x...' // Your token address
 
  const createPolicy = Hooks.policy.useCreateSync({ 
    mutation: { 
      onSuccess(result) { 
        // Store policyId for the next step 
        console.log('Policy ID:', result.policyId) 
      }, 
    }, 
  }) 
 
  const handleCreatePolicy = async () => { 
    await createPolicy.mutateAsync({ 
      addresses: [ 
        '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', 
      ], 
      type: 'blacklist', 
    }) 
  } 
 
  return (
    <button
      disabled={createPolicy.isPending}
      onClick={handleCreatePolicy}
      type="button"
    > 
      {createPolicy.isPending ? 'Creating...' : 'Create Policy'}
    </button> 
  )
}

Pause and Unpause Token Transfers

Temporarily halt all token transfers during emergency situations or maintenance windows. Pausing transfers requires the PAUSE_ROLE. Unpausing transfers requires the UNPAUSE_ROLE.

Pause and Unpause Your Token

demo
1
Create an account, or use an existing one.
2
Add testnet funds to your account.
3
Create & deploy a token to testnet.
4
Grant pause, unpause roles on token.
5
Pause transfers for token.
pnpx gitpick tempoxyz/tempo-ts/tree/main/examples/issuance
PauseUnpauseTransfers.tsx
import React from 'react'
import { Hooks } from 'tempo.ts/wagmi'
 
export function PauseUnpauseTransfers() {
  const tokenAddress = '0x...' // Your token address
 
  const { data: metadata, refetch: refetchMetadata } =
    Hooks.token.useGetMetadata({ token: tokenAddress }) 
 
  const pause = Hooks.token.usePauseSync({ 
    mutation: { onSettled: () => refetchMetadata() }, 
  }) 
 
  const unpause = Hooks.token.useUnpauseSync({ 
    mutation: { onSettled: () => refetchMetadata() }, 
  }) 
 
  const paused = metadata?.paused || false
 
  const handleToggle = () => { 
    if (paused) { 
      unpause.mutate({ 
        token: tokenAddress, 
      }) 
    } else { 
      pause.mutate({ 
        token: tokenAddress, 
      }) 
    } 
  } 
 
  const isProcessing = pause.isPending || unpause.isPending 
 
  return (
    <button
      disabled={isProcessing}
      onClick={handleToggle}
      type="button"
    > 
      {isProcessing ? 'Processing...' : paused ? 'Unpause' : 'Pause'}
    </button> 
  )
}

Using the Burn Blocked Role

The Burn Blocked role allows your team to burn tokens from blocked or frozen addresses. This is useful for regulatory compliance when you need to remove tokens from addresses that violate terms of service or legal requirements.

Create and Link Transfer Policy

demo
1
Create an account, or use an existing one.
2
Add testnet funds to your account.
3
Create & deploy a token to testnet.
4
Grant issuer, burnBlocked roles on token.
5
Mint 100 tokens to recipient.
6
Create a transfer policy.
7
Link the policy to your token.
8
Burn 100 tokens from blocked address.
pnpx gitpick tempoxyz/tempo-ts/tree/main/examples/issuance
BurnBlocked.tsx
import React from 'react'
import { Hooks } from 'tempo.ts/wagmi'
import { parseUnits } from 'viem'
 
export function BurnBlocked() {
  const tokenAddress = '0x...' // Your token address
  const blockedAddress = '0x...' // The blocked address to burn tokens from
 
  const { data: metadata } = Hooks.token.useGetMetadata({ 
    token: tokenAddress, 
  }) 
 
  const burnBlocked = Hooks.token.useBurnBlockedSync() 
 
  const handleBurnBlocked = async () => { 
    if (!metadata) return
 
    await burnBlocked.mutateAsync({ 
      token: tokenAddress, 
      from: blockedAddress, 
      amount: parseUnits('100', metadata.decimals), 
    }) 
  } 
 
  return (
    <button
      disabled={burnBlocked.isPending}
      onClick={handleBurnBlocked}
      type="button"
    > 
      {burnBlocked.isPending ? 'Burning...' : 'Burn Blocked Tokens'}
    </button> 
  )
}

Best Practices

Role Separation

Use different addresses for different roles to enhance security. For example, assign the issuer role to your treasury address for minting, and the pause role to your security team for emergency controls.

Event Monitoring

Monitor onchain events for role changes, mints, burns, and administrative actions to maintain visibility into token operations and detect unauthorized activities.

Emergency Procedures

Ensure pause and unpause roles are assigned to trusted addresses and that your team has documented procedures for responding to security incidents requiring token transfers to be halted.

Learning Resources