Cardano Serialization Lib

repository·master·Indexed 42 days ago

https://github.com/emurgo/cardano-serialization-lib

A Rust-based library for the serialization and deserialization of data structures used in Cardano's Haskell implementation of Alonzo. Version 17.0.0 provides essential utility functions for Cardano developers and is available as a Rust crate and various NPM packages for Node.js, browsers (including ASM.js), and React Native.

Tokens
24.7K
Snippets
81
Records
100
Agent score
53%

What's inside Cardano Serialization Lib

  1. Understand Cardano ledger specifications

    master
    To use the Cardano Serialization Lib effectively, you should have a foundational understanding of how the Cardano ledger operates. The library is designed to work with the formal specifications of the Cardano ledger. It is highly recommended to consult the Cardano Ledger Specifications when working with the library, specifically the Shelley ledger formal specification which covers the core concepts of the network.
  2. Derive keys using BIP32 (BIP32-Ed25519)

    master

    The library distinguishes between raw PublicKey/PrivateKey (used for signing/verifying) and BIP32PublicKey/BIP32PrivateKey. The latter support key derivation following the BIP32-Ed25519 scheme.

    To derive keys following the BIP44 spec for Cardano, use the following derivation path logic:

    • Purpose: Use harden(1852) or harden(44).
    • Coin Type: Use harden(1815).
    • Account: Use harden(0) for the first account.
    • Sub-paths: Use 0 for external/UTXO keys and 2 for chimeric/staking keys.

    Note: harden(num) is a helper function that returns 0x80000000 + num.

    function harden(num: number): number {
      return 0x80000000 + num;
    }
    
    const rootKey = CardanoWasm.BIP32PrivateKey.from_bech32("xprv17qx9vxm6060qjn5fgazfue9nwyf448w7upk60c3epln82vumg9r9kxzsud9uv5rfscxp382j2aku254zj3qfx9fx39t6hjwtmwq85uunsd8x0st3j66lzf5yn30hwq5n75zeuplepx8vxc502txx09ygjgx06n0p");
    const accountKey = rootKey
      .derive(harden(1852)) // purpose
      .derive(harden(1815)) // coin type
      .derive(harden(0)); // account #0
    
    const utxoPubKey = accountKey
      .derive(0) // external
      .derive(0)
      .to_public();
    
    const stakeKey = accountKey
      .derive(2) // chimeric
      .derive(0)
      .to_public();
  3. Understand the example project structure

    master

    The example project is organized into UI components and their corresponding logic. This separation allows you to see how the Cardano Serialization Lib is integrated into functional code:

    • UI Components: Located in ./src/components-ui. These files (e.g., TransactionCreator.tsx) handle the user interface.
    • Logic Components: Located in ./src/components-logic. These files (e.g., TransactionCreator.ts) contain the actual implementation and calls to the library.
    • Core Utilities: Commonly used functions are centralized in ./src/components-logic/core.ts.

    Example Pattern: To implement a new feature, you would create a UI file in components-ui and its corresponding logic file in components-logic with the same name.

  4. Convert a TransactionBody into a signed Transaction

    master

    A TransactionBody is not ready to be posted on the blockchain until it is wrapped in a Transaction object. A Transaction consists of:

    1. The TransactionBody.
    2. A matching TransactionWitnessSet.
    3. (Optional) TransactionMetadata.

    Witness Requirements:

    • The witnesses must sign the hash of the transaction body, obtained via hash_transaction(body).
    • Witnesses are required for inputs, withdrawals, and certain certificates (e.g., stake address de-registration requires a witness, while registration does not).
    • The witnesses and metadata provided must match those used in the TransactionBuilder.
  5. Construct a Minting Policy with Key Hash and Timelock

    master

    To create a secure minting policy, you can combine multiple scripts into a single NativeScript using CardanoWasm.ScriptAll. A common pattern is to use a ScriptPubkey to ensure only the holder of the policy private key can mint, and a TimelockExpiry to ensure the policy cannot be used indefinitely.

    Workflow:

    1. Create a NativeScripts container.
    2. Create a ScriptPubkey from the policy's key hash.
    3. Create a TimelockExpiry using a target slot (TTL).
    4. Add both scripts to the container.
    5. Wrap them in CardanoWasm.ScriptAll and create the final NativeScript.

    This mintScript is then used to calculate the policyId via mintScript.hash().

    const scripts = CardanoWasm.NativeScripts.new();
    
    // 1. Add Key Hash Script
    const policyKeyHash = CardanoWasm.BaseAddress.from_address(policyAddr)
      .payment_cred()
      .to_keyhash();
    const keyHashScript = CardanoWasm.NativeScript.new_script_pubkey(
      CardanoWasm.ScriptPubkey.new(policyKeyHash)
    );
    scripts.add(keyHashScript);
    
    // 2. Add Timelock Script
    const timelock = CardanoWasm.TimelockExpiry.new(policyTtl);
    const timelockScript = CardanoWasm.NativeScript.new_timelock_expiry(timelock);
    scripts.add(timelockScript);
    
    // 3. Combine into a single Mint Script
    const mintScript = CardanoWasm.NativeScript.new_script_all(
      CardanoWasm.ScriptAll.new(scripts)
    );
  6. Understand Cardano Transaction Metadata format

    master

    Transaction metadata (post-Shelley hardfork) is a map of metadatums, which are recursive JSON-like structures. The format is defined using CDDL (Concise Data Definition Language) to represent CBOR binary encoding.

    Metadata Structure

    A transaction_metadata is a map where keys are transaction_metadatum_label (uint) and values are transaction_metadatum.

    A transaction_metadatum can be one of the following:

    • A map of metadatums
    • A list of metadatums
    • An integer
    • A bytestring (max 64 bytes)
    • Text (max 64 bytes)

    Metadata Limitations

    • Strings: Must be at most 64 bytes when UTF-8 encoded.
    • Bytestrings: Must be hex-encoded and have a maximum length of 64 bytes.
  7. Use Metadata structures directly via CardanoWasm

    master

    For maximum flexibility and readability by other methods, you can use the library's built-in metadata structs. This approach requires manual structural validation.

    Core Types

    • TransactionMetadatum: Represents one of the 5 variant types (Map, List, Int, Bytes, Text).
    • MetadataMap: An unordered map (like a JSON object) indexed by metadatums.
    • MetadataList: An ordered list (like a JSON array) indexed starting at 0.
    • Int: Represents signed or unsigned numbers.

    Implementation Example

    To construct a complex metadata object manually:

    const map = CardanoWasm.MetadataMap.new();
    map.insert(
      CardanoWasm.TransactionMetadatum.new_text("receiver_id"),
      CardanoWasm.TransactionMetadatum.new_text("SJKdj34k3jjKFDKfjFUDfdjkfd"),
    );
    // ... add other fields ...
    const tags = CardanoWasm.MetadataList.new();
    tags.add(CardanoWasm.TransactionMetadatum.new_int(CardanoWasm.Int.new(CardanoWasm.BigNum.from_str("0"))));
    map.insert(
      CardanoWasm.TransactionMetadatum.new_text("tags"),
      CardanoWasm.TransactionMetadatum.new_list(tags),
    );
    const metadatum = CardanoWasm.TransactionMetadatum.new_map(map);
    const map = CardanoWasm.MetadataMap.new();
    map.insert(
      CardanoWasm.TransactionMetadatum.new_text("receiver_id"),
      CardanoWasm.TransactionMetadatum.new_text("SJKdj34k3jjKFDKfjFUDfdjkfd"),
    );
    map.insert(
      CardanoWasm.TransactionMetadatum.new_text("sender_id"),
      CardanoWasm.TransactionMetadatum.new_text("jkfdsufjdk34h3Sdfjdhfduf873"),
    );
    map.insert(
      CardanoWasm.TransactionMetadatum.new_text("comment"),
      CardanoWasm.TransactionMetadatum.new_text("happy birthday"),
    );
    const tags = CardanoWasm.MetadataList.new();
    tags.add(CardanoWasm.TransactionMetadatum.new_int(CardanoWasm.Int.new(CardanoWasm.BigNum.from_str("0"))));
    tags.add(CardanoWasm.TransactionMetadatum.new_int(CardanoWasm.Int.new(CardanoWasm.BigNum.from_str("264"))));
    tags.add(CardanoWasm.TransactionMetadatum.new_int(CardanoWasm.Int.new_negative(CardanoWasm.BigNum.from_str("1024"))));
    tags.add(CardanoWasm.TransactionMetadatum.new_int(CardanoWasm.Int.new(CardanoWasm.BigNum.from_str("32"))));
    map.insert(
      CardanoWasm.TransactionMetadatum.new_text("tags"),
      CardanoWasm.TransactionMetadatum.new_list(tags),
    );
    const metadatum = CardanoWasm.TransactionMetadatum.new_map(map);
  8. Convert Metadata to and from JSON

    master

    JSON conversion is the lowest set-up option and is tech-agnostic. Use CardanoWasm.encode_json_str_to_metadatum and CardanoWasm.decode_metadatum_to_json_str with a specified MetadataJsonSchema mode.

    JSON Schema Modes

    • NoConversions: Faithfully converts only the minimal shared feature set between JSON and Metadata.
    • BasicConversions: Adds support for byte/integer keys and byte values.
      • Bytes: Must be prefixed with 0x (e.g., "0xdeadbeef").
      • Integers: Strings that parse as integers (e.g., "125") are treated as metadata integers.
    • DetailedSchema: A highly verbose mode that can represent every possible kind of metadata (including non-string/byte/int keys) by wrapping values in type-tagging objects (e.g., {"bytes": "8badf00d"}).

    Limitations

    • Does not support negative integers between -2^64 + 1 and -2^63 due to serde_json restrictions.
    • Structural validation must be done by hand.
    const obj = {
      receiver_id: "SJKdj34k3jjKFDKfjFUDfdjkfd",
      sender_id: "jkfdsufjdk34h3Sdfjdhfduf873",
      comment: "happy birthday",
      tags: [0, 264, -1024, 32]
    };
    const metadata = CardanoWasm.encode_json_str_to_metadatum(JSON.stringify(obj), CardanoWasm.MetadataJsonSchema.NoConversions);
    const metadataString = CardanoWasm.decode_metadatum_to_json_str(metadata, CardanoWasm.MetadataJsonSchema.NoConversions);
  9. Generate a WASM-compatible library from CDDL

    master

    To create a library for parsing specific metadata structures, use the cddl-codegen tool. This process generates a Rust library that can be converted to WASM for use in JavaScript environments.

    1. Place your input.cddl in the root of the cddl-codegen directory.
    2. Build and run the codegen tool to produce a Rust library in the /export/ directory.
    3. Navigate to /export/ and use wasm-pack to build a Node.js or browser-compatible package.

    This results in a package in the /pkg/ directory that you can import into your project.

    # In cddl-codegen root
    cargo build
    cargo run
    
    # In export directory
    cd export
    wasm-pack build --target=nodejs
    wasm-pack pack
  10. Mint NFTs using Cardano Serialization Lib and Yoroi Backend

    master

    This guide demonstrates how to mint an NFT directly to an account on the Cardano testnet. The process involves creating a minting policy (using a combination of a key hash script and a timelock script), building a transaction with the required metadata (CIP-25 style), signing the transaction with both the policy key and the account key, and submitting it via the Yoroi backend API.

    Key Steps:

    1. Address Generation: Derive the account and policy addresses using CardanoWasm.BaseAddress and CardanoWasm.NetworkInfo.testnet().
    2. UTXO Selection: Fetch available UTXOs from the Yoroi backend to ensure sufficient ADA for fees.
    3. Policy Construction: Create a NativeScript using ScriptAll that combines a ScriptPubkey (to restrict minting to the policy owner) and a TimelockExpiry (to expire the policy after a certain slot).
    4. Transaction Building: Use TransactionBuilder to add inputs, mint assets via add_mint_asset_and_output_min_required_coin, and attach JSON metadata using add_json_metadatum (using label 721).
    5. Signing: Create a TransactionWitnessSet containing Vkeywitnesses for both the policy and account private keys, along with the NativeScripts used in the policy.
    6. Submission: Submit the base64-encoded signed transaction to the Yoroi testnet endpoint.
    import CardanoWasm from "@emurgo/cardano-serialization-lib-nodejs";
    import axios from "axios";
    import cbor from "cbor";
    
    // ... (implementation of mintNft function) ...
    
    const privateKey = CardanoWasm.PrivateKey.from_bech32("ed25519_sk...");
    const policyPrivateKey = CardanoWasm.PrivateKey.from_normal_bytes(
      cbor.decodeFirstSync("582009ca...")
    );
    
    await mintNft(
      privateKey, 
      { privateKey: policyPrivateKey, ttl: null },
      "AssetName",
      "Description",
      "ipfs://...",
      "image/jpeg"
    );