Creating Native Scripts

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

Introduction

This guide provides step-by-step instructions for creating native scripts using the Anvil API's utility endpoints. For conceptual information about native scripts, their structure, and common patterns, see the Native Scripts Overview.

Workflow Overview

The process of creating and using native 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 DateTime to Slot - create a time constraint (optional but recommended)

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

  4. Convert to Native Script and get policy ID - use the Anvil API to create a machine-readable format

  5. Use in Transaction - include the script when minting tokens

Prerequisites

  • An Anvil API key

  • A wallet address for signing transactions

  • Basic understanding of native scripts and their role in minting tokens

Using the Utils Endpoints

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

Endpoint
Description

/utils/addresses/parse

Parses an address and returns its payment and stake credentials

/utils/native-scripts/parse

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

/utils/native-scripts/serialize

Creates a native script from it's JSON schema definition

/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 Native Script for Minting rules.

Step 1: Obtain a key used for policy actions.

To create a signature-based native 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://preprod.api.ada-anvil.app/v2/services/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 native script.

Step 2: Convert Time to Slot (Optional)

To add a time constraint to your native script, convert a future DateTime to a Cardano slot number:

// Example request
await fetch('https://preprod.api.ada-anvil.app/v2/services/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 native script to create a time-limited minting policy.

Step 3: Create JSON Native 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 native script.

Native 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 native script:

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

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

Step 4: Serialize the Native Script via API

While the JSON representation is a clear way to define the native script, it must be serialized into a specific hex format to be included in a transaction. The Anvil API provides a utility endpoint to handle this conversion. Here's how you can call the API with the JSON native script from Step 3:

// The JSON native script from Step 3
const nativeScriptJSON = {
  type: "all",
  scripts: [
    {
      type: "sig",
      // Required Key hash from Step 1
      keyHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    },
    {
      type: "before",
      // Required Slot number from Step 2
      slot: 98765432
    }
  ]
};

async function serializeNativeScript(nativeScriptJSON) {
  const response = await fetch("https://preprod.api.ada-anvil.app/v2/services/utils/native-scripts/serialize", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": "YOUR_API_KEY"
    },
    body: JSON.stringify({ schema: nativeScriptJSON }),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`API call failed: ${errorText}`);
  }

  const { script, policyId } = await response.json();
    
  console.log("CBOR Hex-encoded Script:", script);
  console.log("Policy ID:", policyId);

  return { script, policyId };
}

// Get the serialized script and policy ID
const { script: hexEncodedScript, policyId } = await serializeNativeScript(nativeScriptJSON);

The API returns two crucial pieces of information:

  1. script: The CBOR hex-encoded native script. This is what you will include in the transaction body.

  2. policyId: The unique identifier for your native script, derived from hashing the script.

Step 5: Use in Transaction

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

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, // The policy ID from Step 4
      quantity: 1,

      // Destination address for the minted asset
      destAddress: "addr_test..."
    }
  ],
  // 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 native script from Step 3
        type: "all",
        scripts: [
          {
            type: "sig",
            keyHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
          },
          {
            type: "before",
            slot: 98765432
          }
        ]
      },
      hash: policyId // The policy ID from Step 4
    }
  ]
};

const tx = await fetch(`https://preprod.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.

Additional Native Script Patterns

For more native script patterns including multi-signature policies, threshold requirements, and complex logical combinations, see the Native Scripts Overview.

Conclusion

You now have a complete native script that can be used for minting tokens. The script includes both signature requirements and time constraints, providing security and immutability for your token collection.

For security considerations, best practices, and additional script patterns, refer to the Native Scripts Overview.

Last updated

Was this helpful?