# Compact Standard Library: A Practical Guide to Every Export

The Compact standard library ships with every project — you don't install it, you don't import a package. It's just there. But the docs are sparse enough that most developers only discover what's in it by reading other people's contracts or tripping over a type error. This guide aims to fix that. I'll walk through every meaningful export, grouped by what it actually does, with working code examples for each.

---

## Generic Types: Maybe and Either

These two types show up constantly in real contracts. If you've written any amount of TypeScript or Rust, the concepts will be familiar, but the ZK context adds some nuances worth understanding.

### `Maybe<T>`

`Maybe<T>` is an optional value — it either holds something (`Some(value)`) or it doesn't (`None`). In Compact, it's used wherever you need to represent the absence of a value without resorting to sentinel values like `0` or `-1`.

```compact
contract MaybeExample {
  ledger owner: Maybe<ContractAddress>;

  circuit setOwner(addr: ContractAddress): [] {
    ledger.owner = Maybe.some(addr);
  }

  circuit clearOwner(): [] {
    ledger.owner = Maybe.none();
  }

  circuit checkOwner(addr: ContractAddress): [Boolean] {
    const current = ledger.owner;
    return [current.isSome && current.value == addr];
  }
}
```

The key properties: `.isSome` (a Boolean), `.isNone`, and `.value` (the inner value, only safe to access when you know it's `Some`). Accessing `.value` on a `None` in a ZK circuit causes a constraint failure — it doesn't throw an exception, it makes the proof impossible to generate. Keep that in mind.

A practical pattern is using `Maybe<T>` for optional ledger fields that get initialized lazily:

```compact
circuit initializeIfNeeded(config: Uint<64>): [] {
  if ledger.config.isNone {
    ledger.config = Maybe.some(config);
  }
}
```

### `Either<L, R>`

`Either<L, R>` represents one of two possible types — a `Left(L)` or a `Right(R)`. By convention, `Left` is used for error states and `Right` for success, but Compact doesn't enforce this — it's just a tagged union.

```compact
circuit divide(a: Uint<64>, b: Uint<64>): [Either<Bytes<32>, Uint<64>>] {
  if b == 0 {
    return [Either.left(bytes("division by zero"))];
  }
  return [Either.right(a / b)];
}
```

You can discriminate on `Either` with `.isLeft`, `.isRight`, `.leftValue`, and `.rightValue`. Same caveat as `Maybe` — accessing the wrong branch fails proof generation.

`Either` is less common than `Maybe` in practice but comes up when you need to return structured errors from a circuit, or when modeling two distinct outcomes of a ledger operation (think: deposit vs. withdrawal result).

---

## Merkle Tree Utilities

Merkle trees are central to ZK contract design. The standard library gives you the tree type itself plus the commitment and verification tools you need to do privacy-preserving state proofs.

### `MerkleTree<N, T>`

`MerkleTree<N, T>` is a complete binary Merkle tree with `N` levels (so 2^N leaves) holding values of type `T`. The tree is stored in the ledger and supports O(log N) membership proofs.

```compact
contract Registry {
  ledger members: MerkleTree<20, Bytes<32>>;
  ledger memberCount: Uint<64>;

  circuit addMember(commitment: Bytes<32>): [Uint<64>] {
    const index = ledger.memberCount;
    ledger.members.set(index, commitment);
    ledger.memberCount = index + 1;
    return [index];
  }
}
```

The tree size is fixed at compile time via `N`. If you need more than 2^N entries, you're out of luck without a redesign. Plan ahead — 20 levels gives you ~1 million entries, which is usually enough for registry-style contracts.

### `persistentCommit`

`persistentCommit` creates a commitment to a value that gets stored in the Midnight state tree. This is distinct from just hashing — the commitment gets anchored to a specific epoch, making it possible to prove "this value existed at this point in time" without revealing the value.

```compact
circuit commitSecret(witness secret: Bytes<32>): [Bytes<32>] {
  const commitment = persistentCommit(secret);
  ledger.commitments.push(commitment);
  return [commitment];
}
```

The commitment is deterministic for a given secret — same input always produces the same output — which is what makes nullifier-style patterns work. More on that in the replay prevention article.

### `verifyCommitment`

`verifyCommitment` is the counterpart to `persistentCommit`. Given a secret and a commitment, it verifies that the commitment was generated from that secret.

```compact
circuit revealSecret(
  witness secret: Bytes<32>,
  public commitment: Bytes<32>
): [Boolean] {
  return [verifyCommitment(secret, commitment)];
}
```

This pattern — commit now, reveal later — is fundamental to privacy-preserving voting, sealed bidding, and ownership proofs. Commit on-chain, reveal off-chain when needed.

---

## Elliptic Curve Types

Midnight's ZK circuits operate over elliptic curves under the hood, but the standard library surfaces some of this for contracts that need to do cryptographic operations explicitly.

### `CurvePoint`

`CurvePoint` represents a point on the elliptic curve used by Midnight's proof system. You interact with these when doing key derivation, signature verification, or building custom commitment schemes.

```compact
circuit derivePublicKey(witness privateKey: Scalar): [CurvePoint] {
  const G = CurvePoint.generator();
  return [G.multiply(privateKey)];
}
```

The main operations: `.multiply(scalar)`, `.add(other)`, `.negate()`, and the static `.generator()` which returns the base point. All operations are group operations in the ZK circuit — they generate constraints, so they're not "free."

### Scalar Operations

Scalars are the field elements that serve as private keys and randomness in elliptic curve operations. The scalar type wraps a field element with operations appropriate for the scalar field.

```compact
circuit blindValue(
  witness value: Uint<64>,
  witness blinding: Scalar
): [CurvePoint] {
  const H = CurvePoint.hashToCurve(bytes("value-commitment"));
  const G = CurvePoint.generator();
  // Pedersen commitment: value*H + blinding*G
  return [H.multiply(Scalar.fromUint(value)).add(G.multiply(blinding))];
}
```

The Pedersen commitment pattern above is worth knowing — it's a standard technique for committing to a value while keeping it hidden, with the property that commitments are additively homomorphic.

---

## Kernel Types

These types bridge the ZK contract world and the Midnight network's transaction kernel. They're how your contract talks about identity, addresses, and shielded coin state.

### `ContractAddress`

`ContractAddress` is the canonical identity type for smart contracts on Midnight. It's derived from the contract's code hash and deployment parameters — it's deterministic and unique.

```compact
contract AccessControlled {
  ledger admin: ContractAddress;

  circuit initialize(adminAddr: ContractAddress): [] {
    assert ledger.admin == ContractAddress.zero() : "already initialized";
    ledger.admin = adminAddr;
  }

  circuit adminAction(): [] {
    // thisContractAddress() gives you the calling contract's address
    assert ledger.admin == ContractAddress.self() : "unauthorized";
  }
}
```

Use `ContractAddress` (not `ownPublicKey()`) when you need identity-based access control. The contract address is derived from verifiable on-chain data; `ownPublicKey()` is not. This distinction matters a lot — see the separate article on `ownPublicKey()` risks.

### `ZswapCoinPublicKey`

`ZswapCoinPublicKey` is the public key type for Zswap coin ownership — the shielded asset system on Midnight. When a user deposits shielded tokens, you'll store their `ZswapCoinPublicKey` to represent who has claim to those tokens.

```compact
ledger depositor: ZswapCoinPublicKey;

circuit recordDepositor(pk: ZswapCoinPublicKey): [] {
  ledger.depositor = pk;
}
```

This type is opaque — you can store it, compare it, and pass it to the shielded transfer functions, but you can't decompose it into its underlying curve points without dropping to the lower-level elliptic curve API.

### `UserAddress`

`UserAddress` is a higher-level address type that wraps both a `ZswapCoinPublicKey` and a spending key derivation path. It's what users expose publicly when they want to receive funds.

```compact
circuit getRecipientKey(userAddr: UserAddress): [ZswapCoinPublicKey] {
  return [userAddr.spendingKey()];
}
```

Think of `UserAddress` as the "public address you share" and `ZswapCoinPublicKey` as the "internal representation used for coin operations." They're related but not interchangeable.

### `ShieldedCoinInfo` and `QualifiedShieldedCoinInfo`

`ShieldedCoinInfo` describes a shielded coin: its value, token type, and randomness. `QualifiedShieldedCoinInfo` adds the nullifier key needed to spend it.

```compact
ledger heldCoin: QualifiedShieldedCoinInfo;

circuit inspectHeldCoin(): [Uint<64>] {
  return [ledger.heldCoin.info.value];
}
```

You rarely construct these manually — they come from `receiveShielded` and get consumed by `sendShielded`. But knowing the structure helps when you need to inspect or route coins based on their value or type.

---

## Helper Circuits

These are the high-level operations for working with tokens and shielded transfers. They're the most practically useful exports for contracts that handle money.

### `nativeToken()`

Returns the token type identifier for MNT, Midnight's native token. This is a `Bytes<32>` constant that identifies MNT in mixed-asset contexts.

```compact
circuit acceptNativeOnly(coinType: Bytes<32>): [] {
  assert coinType == nativeToken() : "only MNT accepted";
}
```

Straightforward, but important — don't hardcode the native token bytes. Use this function so your contract works correctly across testnet/mainnet where the token identifier might differ.

### `tokenType(contractAddr: ContractAddress)`

Derives the token type identifier for a custom token contract. Every token on Midnight is identified by the contract that minted it.

```compact
circuit getMyTokenType(): [Bytes<32>] {
  return [tokenType(ContractAddress.self())];
}

circuit isMyToken(coinType: Bytes<32>): [Boolean] {
  return [coinType == tokenType(ContractAddress.self())];
}
```

If you're building a DEX or multi-token vault, you'll use this to route coins to the correct handling logic based on their type.

### `receiveShielded`

`receiveShielded` processes an incoming shielded coin transfer. It validates the coin proof, extracts the coin information, and returns a `QualifiedShieldedCoinInfo` that your contract can store or act on.

```compact
circuit deposit(witness coinProof: CoinProof): [QualifiedShieldedCoinInfo] {
  const coin = receiveShielded(coinProof);
  assert coin.info.tokenType == nativeToken() : "MNT only";
  ledger.balance = ledger.balance + coin.info.value;
  ledger.heldCoin = coin;
  return [coin];
}
```

The `coinProof` is a private witness — the depositor provides it off-chain, and the circuit verifies it without revealing the coin's details to the public ledger. The returned `QualifiedShieldedCoinInfo` contains everything you need to later spend or transfer the coin.

### `sendShielded`

`sendShielded` is the counterpart — it constructs and sends a shielded transfer to a recipient.

```compact
circuit withdraw(
  recipient: ZswapCoinPublicKey,
  amount: Uint<64>
): [] {
  assert amount <= ledger.balance : "insufficient balance";
  ledger.balance = ledger.balance - amount;
  sendShielded(recipient, ledger.heldCoin, amount);
}
```

The actual coin details stay hidden from chain observers. What's visible: the transfer happened, the contract's ledger state changed. What's hidden: the amount, the recipient's identity, and the coin's lineage.

---

## Putting It Together: A Token Vault

Here's a minimal but complete example using several stdlib exports together:

```compact
import * from "stdlib";

contract TokenVault {
  ledger deposits: MerkleTree<16, Bytes<32>>;
  ledger depositCount: Uint<64>;
  ledger totalBalance: Uint<64>;
  ledger heldCoins: MerkleTree<16, QualifiedShieldedCoinInfo>;

  circuit deposit(witness coinProof: CoinProof): [Uint<64>] {
    const coin = receiveShielded(coinProof);
    assert coin.info.tokenType == nativeToken() : "MNT only";

    const index = ledger.depositCount;
    ledger.heldCoins.set(index, coin);
    ledger.totalBalance = ledger.totalBalance + coin.info.value;
    ledger.depositCount = index + 1;

    // Commitment to this deposit for the Merkle proof
    const commitment = persistentCommit(
      coin.info.value.toBytes().concat(index.toBytes())
    );
    ledger.deposits.set(index, commitment);

    return [index];
  }

  circuit withdraw(
    coinIndex: Uint<64>,
    recipient: ZswapCoinPublicKey
  ): [] {
    const coin = ledger.heldCoins.get(coinIndex);
    assert coin.isSome : "coin not found";
    ledger.totalBalance = ledger.totalBalance - coin.value.info.value;
    sendShielded(recipient, coin.value, coin.value.info.value);
    ledger.heldCoins.set(coinIndex, Maybe.none());
  }
}
```

---

## Quick Reference

| Export | Category | Use For |
|--------|----------|---------|
| `Maybe<T>` | Generic | Optional values, nullable ledger fields |
| `Either<L,R>` | Generic | Two-outcome results, structured errors |
| `MerkleTree<N,T>` | Merkle | Fixed-size on-chain indexed storage |
| `persistentCommit` | Merkle | Creating verifiable commitments |
| `verifyCommitment` | Merkle | Verifying commitments in proofs |
| `CurvePoint` | Crypto | Key derivation, Pedersen commitments |
| `Scalar` | Crypto | Private keys, blinding factors |
| `ContractAddress` | Kernel | Contract identity, access control |
| `ZswapCoinPublicKey` | Kernel | Shielded coin recipient keys |
| `UserAddress` | Kernel | User-facing addresses |
| `ShieldedCoinInfo` | Kernel | Coin value/type metadata |
| `QualifiedShieldedCoinInfo` | Kernel | Spendable coin with nullifier key |
| `nativeToken()` | Tokens | MNT token type identifier |
| `tokenType(addr)` | Tokens | Custom token type identifier |
| `receiveShielded` | Transfers | Accept incoming shielded coins |
| `sendShielded` | Transfers | Send shielded coins to recipient |

---

## Final Notes

The standard library is deliberately minimal. Midnight's philosophy is to give you the cryptographic building blocks and let you compose them — rather than shipping high-level abstractions that bake in assumptions about your use case.

The types you'll use in every contract: `Maybe<T>`, `ContractAddress`, `receiveShielded`, `sendShielded`. Everything else is situational. Learn the Merkle utilities when you need provable state membership. Learn the elliptic curve types when you're building custom commitment schemes or signature verification. Learn `Either<L,R>` when you need structured circuit outputs.

The biggest gotcha for newcomers: all of these operations happen inside ZK circuits. "Optional" doesn't mean "try/catch" — it means "the proof is either valid or it isn't." Design your circuits with that constraint in mind.

