Fleet SDK Recipes

This page contains useful code snippets, patterns, and troubleshooting tips for common tasks when using the Fleet SDK in TypeScript/JavaScript.

Validating Box Ownership (SigmaProp from Register)

Validate if an input box belongs to a specific owner whose public key (SigmaProp) is stored in another box's register. This is often needed for refund scenarios or proving ownership before allowing an action.

  1. Extract owner's SigmaProp bytes from a register (e.g., R4).
  2. Deserialize these bytes.
  3. Convert bytes into the owner's P2PK ErgoTree.
  4. Compare with the ErgoTree of the box being validated.

Important: Directly constructing ErgoAddress from SSigmaProp is not the correct approach in Fleet SDK. Extract public key bytes first.

Complete Example:

import { Box, ErgoAddress } from "@fleet-sdk/core";
import { deserialize } from "@fleet-sdk/serializer";

function validateBoxOwnership(boxToValidate: Box, boxWithOwnerInfo: Box): boolean {
  try {
    const registerValueHex = boxWithOwnerInfo.additionalRegisters.R4;
    if (!registerValueHex) {
      return false;
    }
    const deserializedValue = deserialize(registerValueHex);
    let publicKeyBytes: Uint8Array;
    if (deserializedValue.type === "SColl" && deserializedValue.elemType === "SByte") {
      publicKeyBytes = new Uint8Array(deserializedValue.value);
    } else if (deserializedValue.type === "SSigmaProp") {
      if (deserializedValue.value?.type === "SGroupElement") {
        publicKeyBytes = new Uint8Array(deserializedValue.value.value);
      } else {
        return false;
      }
    } else {
      return false;
    }
    if (!publicKeyBytes || publicKeyBytes.length !== 33) {
      return false;
    }
    const ownerAddress = ErgoAddress.fromPublicKey(publicKeyBytes);
    const ownerErgoTree = ownerAddress.ergoTree;
    return boxToValidate.ergoTree === ownerErgoTree;
  } catch {
    return false;
  }
}

Working with Numeric Registers

import { Box } from "@fleet-sdk/core";
import { deserialize } from "@fleet-sdk/serializer";

function getNumericFromRegister(box: Box, register: "R4" | "R5" | "R6" | "R7" | "R8" | "R9"): bigint | null {
  try {
    const registerValueHex = box.additionalRegisters[register];
    if (!registerValueHex) return null;
    const deserialized = deserialize(registerValueHex);
    if (deserialized.type === "SLong") {
      return BigInt(deserialized.value);
    } else if (deserialized.type === "SInt") {
      return BigInt(deserialized.value);
    }
    return null;
  } catch {
    return null;
  }
}

Extracting Token IDs from Registers

import { Box } from "@fleet-sdk/core";
import { deserialize } from "@fleet-sdk/serializer";
import { Buffer } from "buffer";

function getTokenIdFromRegister(box: Box, register: "R4" | "R5" | "R6" | "R7" | "R8" | "R9"): string | null {
  try {
    const registerValueHex = box.additionalRegisters[register];
    if (!registerValueHex) return null;
    const deserialized = deserialize(registerValueHex);
    if (deserialized.type === "SColl" && deserialized.elemType === "SByte") {
      return Buffer.from(deserialized.value).toString("hex");
    }
    return null;
  } catch {
    return null;
  }
}

Decoding Complex Types (Tuples, Options, Collections)

import { Box } from "@fleet-sdk/core";
import { deserialize } from "@fleet-sdk/serializer";
import { Buffer } from "buffer";

function decodeSigmaPropLongTuple(box: Box, register: "R4"|"R5"|"R6"|"R7"|"R8"|"R9") {
  try {
    const registerValueHex = box.additionalRegisters[register];
    if (!registerValueHex) return null;
    const deserialized = deserialize(registerValueHex);
    if (deserialized.type !== "STuple" || !Array.isArray(deserialized.value) || deserialized.value.length !== 2) {
      return null;
    }
    const [item1, item2] = deserialized.value;
    let ownerPubKeyBytes: Uint8Array | null = null;
    if (item1?.type === "SSigmaProp" && item1.value?.type === "SGroupElement" && item1.value.value) {
      ownerPubKeyBytes = new Uint8Array(item1.value.value);
    } else {
      return null;
    }
    if (!ownerPubKeyBytes || ownerPubKeyBytes.length !== 33) return null;
    const ownerPubKeyHex = Buffer.from(ownerPubKeyBytes).toString("hex");
    let deadline: bigint | null = null;
    if (item2?.type === "SLong" && typeof item2.value === "string") {
      deadline = BigInt(item2.value);
    } else {
      return null;
    }
    return { ownerPubKeyHex, deadline };
  } catch {
    return null;
  }
}

Compiling ErgoScript to ErgoTree

import { compile } from "@fleet-sdk/compiler";

async function compileToErgoTree(script: string): Promise<string> {
  try {
    const result = await compile(script);
    if (!result || !result.ergoTree) throw new Error("No ErgoTree.");
    return result.ergoTree;
  } catch (error) {
    throw error;
  }
}

Troubleshooting Common Issues

ErgoTree Comparison Failures

function troubleshootErgoTreeComparison(ergoTree1: string, ergoTree2: string): string {
  if (ergoTree1 === ergoTree2) return "Exact Match: Trees are identical.";
  const p2pkPrefix = "0008cd";
  const tree1IsP2PK = ergoTree1.startsWith(p2pkPrefix);
  const tree2IsP2PK = ergoTree2.startsWith(p2pkPrefix);
  if (tree1IsP2PK && tree2IsP2PK) {
    if (ergoTree1.length !== ergoTree2.length) {
      return "Mismatch: Both seem P2PK but have different lengths.";
    }
    return "Mismatch: Both seem P2PK with same length, but represent different public keys.";
  } else if (tree1IsP2PK !== tree2IsP2PK) {
    return `Mismatch: One tree appears P2PK (${p2pkPrefix} prefix), the other does not.`;
  } else {
    if (ergoTree1.length !== ergoTree2.length) {
      return "Mismatch: Neither seems standard P2PK, and lengths differ.";
    }
    return "Mismatch: Neither seems standard P2PK, but lengths match.";
  }
}

Unexpected Register Format

function detectRegisterFormat(registerHex: string): string {
  if (!registerHex || registerHex.length < 2) return "Invalid or empty register value";
  const prefix = registerHex.substring(0, 2);
  const lengthByte = parseInt(registerHex.substring(2, 4), 16);
  if (prefix === "0e") {
    if (registerHex.length === 68) return "Likely a Blake2b256 hash (0e + 20 + 32 bytes)";
    return `Likely some hash or ID prefixed with 0e, length byte ${lengthByte}`;
  } else if (prefix === "00") {
    if (registerHex.startsWith("0008cd")) return "Likely a P2PK ErgoTree";
    return "Starts with 00, possibly complex ErgoTree or other structure.";
  } else if (prefix === "04") {
    return "Likely an SInt (Integer)";
  } else if (prefix === "05") {
    return "Likely an SLong (Long Integer / BigInt)";
  } else if (prefix === "07") {
    return `Likely a CollByte (serialized byte array), length byte indicates ${lengthByte} bytes follow`;
  } else if (prefix === "cd") {
    return "Likely a raw SSigmaProp (without 00 ErgoTree wrapper)";
  } else {
    return `Unknown format starting with prefix ${prefix}`;
  }
}

Relationship Diagram

graph TD
  A[Ergo Address String] -->|"ErgoAddress decode"| B[Public Key Bytes]
  B -->|"ErgoAddress fromPublicKey"| C[ErgoAddress Object]
  B -->|"SGroupElement"| D[SGroupElement Object]
  D -->|"SSigmaProp"| E[SSigmaProp Object]
  C -->|"address ergoTree"| F[ErgoTree Hex - P2PK Script]
  E -->|"ErgoAddress sigmaProp ergoTree"| F