Lesson 2 of 6

Lesson 203.2 β€” Minting & Burning Tokens with Native Scripts

Course: Cardano Go PBL 2026Module: 203 β€” Applications & Smart Contracts


🎯 Learning Outcome

I can build a transaction that mints or burns tokens using a native script (no smart contract required).


01 β€” Background: What Is a Native Script?

On Cardano, every token belongs to a policy. A policy defines:

  • Who can mint or burn tokens
  • Under what conditions

Types of Minting Policies

Type

Description

Native Script

Declarative rule set evaluated by the node. No Plutus VM required. Supports signatures and time-locks.

Plutus Script (Validator)

Arbitrary logic executed in Plutus VM. Supports complex conditions and state checks.

πŸ‘‰ This lesson focuses on native scripts.

Key Concept

  • A policy ID = hash of the script
  • Token identity = policyId + assetName

⚠️ Tokens are permanently bound to their policy.


02 β€” How Apollo Models Minting

API Methods

// Native scripts
MintAssets(mintUnit Unit) *Apollo

// Plutus scripts
MintAssetsWithRedeemer(mintUnit Unit, redeemer Redeemer) *Apollo

Unit Structure

unit := apollo.NewUnit(
    "a1b2c3...", // policy ID
    "MyToken",   // asset name
    1000,        // quantity (+ mint, - burn)
)

⚠️ Minted tokens must be included in an output (PayToAddressBech32) or the transaction will fail.


03 β€” Setup: Imports & Backend

package main

import (
    "encoding/hex"
    "fmt"

    "github.com/Salvionied/apollo"
    "github.com/Salvionied/apollo/constants"
    "github.com/Salvionied/apollo/txBuilding/Backend/BlockFrostChainContext"

    NativeScript "github.com/Salvionied/apollo/serialization/NativeScript"
    Key          "github.com/Salvionied/apollo/serialization/Key"
)

const (
    BLOCKFROST_KEY = "previewXXXXXXXXXXXXXXXX"
    MNEMONIC       = "your mnemonic..."
)

04 β€” Native Script & Policy ID

func buildNativeScript(vkey Key.VerificationKey) (NativeScript.NativeScript, string) {
    pkh := vkey.PaymentKeyHash()

    script := NativeScript.NativeScript{
        Type:    NativeScript.ScriptPubkey,
        KeyHash: pkh,
    }

    policyId := hex.EncodeToString(script.Hash())

    return script, policyId
}

Optional: Time-Locked Policy

timeLocked := NativeScript.NativeScript{
    Type: NativeScript.ScriptAll,
    Scripts: []NativeScript.NativeScript{
        {
            Type:    NativeScript.ScriptPubkey,
            KeyHash: pkh,
        },
        {
            Type: NativeScript.ScriptInvalidHereAfter,
            Slot: 10_000_000,
        },
    },
}

05 β€” Complete Minting Transaction

func main() {
    bfc, err := BlockFrostChainContext.NewBlockfrostChainContext(
        constants.BLOCKFROST_BASE_URL_PREVIEW,
        int(constants.PREVIEW),
        BLOCKFROST_KEY,
    )
    if err != nil { panic(err) }

    apollob := apollo.New(&bfc)
    apollob, _ = apollob.SetWalletFromMnemonic(MNEMONIC, constants.PREVIEW)
    apollob, _ = apollob.SetWalletAsChangeAddress()

    utxos, _ := bfc.Utxos(*apollob.GetWallet().GetAddress())

    vkey := apollob.GetWallet().GetVerificationKey()
    script, policyId := buildNativeScript(vkey)

    mintUnit := apollo.NewUnit(policyId, "GimbalToken", 1_000_000)

    apollob, err = apollob.
        AddLoadedUTxOs(utxos...).
        MintAssets(mintUnit).
        AttachNativeScript(script).
        AddRequiredSignerFromBech32(
            apollob.GetWallet().GetAddress().ToBech32(),
            true, false,
        ).
        PayToAddressBech32(
            apollob.GetWallet().GetAddress().ToBech32(),
            2_000_000,
            mintUnit,
        ).
        Complete()

    if err != nil { panic(err) }

    apollob = apollob.Sign()
    txId, _ := apollob.Submit()

    fmt.Println("Tx:", hex.EncodeToString(txId.Payload))
}

06 β€” Burning Tokens

burnUnit := apollo.NewUnit(policyId, "GimbalToken", -500_000)

tokenUtxo, _ := apollob.UtxoFromRef("txhash...", 0)

apollob, err = apollob.
    AddLoadedUTxOs(utxos...).
    AddInput(*tokenUtxo).
    MintAssets(burnUnit).
    AttachNativeScript(script).
    AddRequiredSignerFromBech32(
        apollob.GetWallet().GetAddress().ToBech32(),
        true, false,
    ).
    Complete()

⚠️ Burn amount must not exceed available tokens.


07 β€” Observing with Adder

{
  "txHash": "abc123...",
  "mint": {
    "policyId...": {
      "GimbalToken": 1000000
    }
  },
  "outputs": [
    {
      "address": "addr_test1...",
      "value": {
        "lovelace": 2000000,
        "policyId...GimbalToken": 1000000
      }
    }
  ]
}

08 β€” Step-by-Step Flow

  • Set up Blockfrost context
  • Load wallet
  • Build native script β†’ derive policy ID
  • Create mint unit
  • Build transaction
  • Sign & submit

09 β€” Exercises

A β€” Mint Token

  • Set up backend
  • Derive policy ID
  • Mint 1,000,000 tokens
  • Send to your wallet
  • Verify on explorer

B β€” Burn Half

  • Locate token UTxO
  • Burn 500,000
  • Use same policy
  • Verify negative mint

C β€” Time-Locked Policy

  • Get current slot
  • Create expiry policy
  • Mint before deadline
  • Try minting after (should fail)

10 β€” Knowledge Check

  • Understand native vs Plutus policies
  • Know how policy ID is derived
  • Can construct Unit correctly
  • Can build mint & burn transactions
  • Understand why outputs are required
  • Can interpret mint field

11 β€” What’s Next

Next: Plutus Minting Validators (203.3)

  • MintAssetsWithRedeemer
  • On-chain logic with Aiken
  • Advanced minting rules