Creating Policy Scripts

Comprehensive guide to creating and managing policy scripts for minting tokens on Cardano using the Anvil API.

Introduction

Policy scripts are crucial components when minting tokens on Cardano. They define who has permission to mint or burn assets under a specific policy ID. This guide will walk you through creating policy scripts using the Anvil API's utility endpoints.

Workflow Overview

The process of creating and using policy scripts with the Anvil API follows these key steps:

  1. Parse Wallet Address - obtain key hash(es) for signature requirements (supports multisig with multiple addresses)

  2. Convert Date to Slot - create a time constraint (optional but recommended)

  3. Create JSON Policy Script - combine key hash(s) and time constraints

  4. Convert to Native Script - use CSL to create a machine-readable format

  5. Generate Policy ID - derive the unique identifier for your assets

  6. Use in Transaction - include the policy when minting tokens

Policy Script Creation Workflow
Complete workflow for creating and using policy scripts with the Anvil API

Prerequisites

  • An Anvil API key

  • A new dedicated private wallet for your token collection (preferred) or an existing wallet address that will be used for signing, minting, and burning transactions

  • Understanding of policy scripts and their role in minting tokens

Using the Utils Endpoints

The Anvil API provides several utility endpoints to assist with creating and managing policy scripts:

Endpoint
Description

/utils/addresses/parse

Parses an address and returns its payment and stake credentials

/utils/native-scripts/get-info

Returns information about a provided native script (e.g., policy ID)

/utils/network/time-to-slot

Converts a date/time to a Cardano slot number

/utils/network/slot-to-time

Converts a Cardano slot number to a date/time

Step-by-Step Guide to Creating a Policy Script

Step 1: Obtain a key used for policy actions.

To create a signature-based policy script, you first need to obtain the key hash of the wallet that will sign the minting transactions. You can get this by parsing a wallet address using the address parsing endpoint:

// Example request
await fetch('https://prod.api.ada-anvil.app/v2/utils/addresses/parse', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    address: 'stake_address_or_payment_address_here'
  })
});

// Example response
{
  "payment": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "stake": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
}

The payment value from the response is what you'll use as the keyHash in your policy script.

Step 2: Convert Date to Slot

For NFT collections, adding a time constraint ensures your collection becomes immutable after a certain date. To add a time constraint, you need to convert a future date to a Cardano slot number using the time-to-slot endpoint:

// Example request
await fetch('https://prod.api.ada-anvil.app/v2/utils/network/time-to-slot', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    timestamp: '2030-01-01T00:00:00Z'
  })
});

// Example response
{
  "slot": 98765432
}

This slot number will be used in your policy script to create a time-limited minting policy. While this step is optional, it's highly recommended for NFT collections to ensure they cannot be minted indefinitely.

Step 3: Create JSON Policy Script

Now that you have both the key hash (from Step 1) and optionally a slot number for time constraint (from Step 2), you can create the JSON structure for your policy script.

Policy scripts in the Anvil API follow a specific JSON structure that maps to Cardano's native script format. Each script uses a discriminated union with a type field that determines the script's behavior.

Here's how to combine the signature requirement and time constraint into a complete policy script:

{
  "type": "all",
  "scripts": [
    {
      "type": "sig",
      // Key hash from Step 1
      "keyHash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    },
    {
      "type": "before",
      // Slot number from Step 2
      "slot": 98765432
    }
  ]
}

This JSON structure creates a policy that requires both the signature from the specified key hash AND the transaction to be submitted before the specified slot number.

Step 4: Convert to Native Script

While the JSON representation is useful for understanding, to actually use the policy script on the Cardano blockchain, you need to convert it to a native script format using the Cardano Serialization Library (CSL).

What is CSL? The Cardano Serialization Library (CSL) is a collection of tools that allows developers to interact with the Cardano blockchain at a low level. It provides functions to create, serialize, and deserialize Cardano data structures, including native scripts used for policy scripts.

import {
  Ed25519KeyHash,
  NativeScript,
  NativeScripts,
  ScriptAll,
  ScriptPubkey,
  TimelockExpiry
} from "@emurgo/cardano-serialization-lib-nodejs";

// Function to create a policy script with CSL
function createPolicyScript(policyKeyHash, ttl, withTimelock = true) {
  // Create a collection of scripts
  const scripts = NativeScripts.new();

  // Add the signature requirement
  const keyHashScript = NativeScript.new_script_pubkey(
    ScriptPubkey.new(Ed25519KeyHash.from_hex(policyKeyHash))
  );
  scripts.add(keyHashScript);

  // Add the timelock if requested
  if (withTimelock) {
    const timelock = TimelockExpiry.new(ttl);
    const timelockScript = NativeScript.new_timelock_expiry(timelock);
    scripts.add(timelockScript);
  }

  // Combine everything with an 'all' script
  const mintScript = NativeScript.new_script_all(ScriptAll.new(scripts));

  return mintScript;
}

This function converts the conceptual JSON policy script from Step 3 into a real native script object. Here's how you would use it with the values obtained in previous steps:

// Create the native script using the function above
const nativeScript = createPolicyScript(
  "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", // key hash from Step 1
  98765432, // slot number from Step 2
  true // include timelock
);

// Convert to hex format for use with APIs
const hexEncodedScript = nativeScript.to_hex();

Step 5: Generate Policy ID

Once you have created the native script, you can generate the policy ID. This unique identifier will be associated with all tokens minted under this policy.

You can get the policy ID by using the Anvil API endpoint:

// Get the hex representation for the API
const hexEncodedScript = nativeScript.to_hex();

// Use the API to get the policy ID
await fetch('https://prod.api.ada-anvil.app/v2/utils/native-scripts/get-info', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    script: hexEncodedScript
  })
});

// Example response
{
  "policyId": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
}

Step 6: Use in Transaction

The final step is to use your policy script when building transactions that mint or burn assets. You'll include the policy script in the preloadedScripts section of your transaction request:

TODO: Update payload based on changes made to the metadata

const data = {
  changeAddress: "addr_test...",
  utxos: ["8282...", "8282..."],
  mint: [
    {
      version: "cip25",
      assetName: { name: "MyAsset", format: "utf8" },
      metadata: {
        name: "My Asset",
        image: ["ipfs://Qm..."],
        // Additional metadata fields
      },
      policyId: policyId, // The policy ID from Step 5
      quantity: 1
    }
  ],
  outputs: [
    {
      address: "recipient_address",
      assets: [
        {
          assetName: "MyAsset",
          policyId: policyId,
          quantity: 1
        }
      ]
    }
  ],
  // This is required for the first mint transaction. Anvil API will fetch the scripts from the provided policy ID for any subsequent transactions.
  preloadedScripts: [
    {
      type: "simple",
      script: {
        // Your policy script from Step 3
        type: "all",
        scripts: [
          {
            type: "sig",
            keyHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
          },
          {
            type: "before",
            slot: 98765432
          }
        ]
      },
      hash: policyId // The policy ID from Step 5
    }
  ]
};

const tx = await fetch(`https://prod.api.ada-anvil.app/v2/services/transactions/build`, {
  method: "POST",
  body: JSON.stringify(data),
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": "YOUR_API_KEY"
  }
});

This transaction, when signed and submitted, will mint a new asset under the policy ID you generated.

Common Policy Script Patterns

Basic Single-Signature Policy

{
  "type": "sig",
  "keyHash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
{
  "type": "all",
  "scripts": [
    {
      "type": "sig",
      "keyHash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    },
    {
      "type": "before",
      "slot": 98765432
    }
  ]
}

Multi-Signature Policy (Any of Two Keys)

{
  "type": "any",
  "scripts": [
    {
      "type": "sig",
      "keyHash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    },
    {
      "type": "sig",
      "keyHash": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
    }
  ]
}

Multi-Signature Policy with Threshold (2 of 3 Keys Required)

{
  "type": "atLeast",
  "required": 2,
  "scripts": [
    {
      "type": "sig",
      "keyHash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    },
    {
      "type": "sig",
      "keyHash": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
    },
    {
      "type": "sig",
      "keyHash": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
    }
  ]
}

Conclusion

Creating policy scripts is a fundamental step in minting tokens on Cardano. By using the Anvil API utility endpoints, you can easily construct policy scripts with various constraints to match your project's requirements.

Remember to consider security implications when designing your policy scripts:

  1. For NFT collections, using a time-limited policy provides authenticity and immutability.

  2. For fungible tokens that may require future minting, consider using a multi-signature approach without time constraints.

  3. Always keep your signing keys secure, as they are crucial for maintaining control over your minting ability.

  4. Use dedicated policy keys only for signing minting and burning operations, not for other transactions.

  5. Consider creating a separate treasury wallet to collect ADA from sales, keeping it separate from minting operations.

Last updated

Was this helpful?