# HinkalBase

#### Overview

HinkalBase owns the canonical state that backs private assets: the Merkle tree of commitments, the ledger of spent nullifiers, and the global configuration for helper and logic modules. While higher-level contracts orchestrate flows, this base ensures that when a transaction finishes, the state transition is sound, indexable, and future-proof.

**Storage**

* `mapping(uint256 => bool) public nullifiers` — Tracks used nullifiers to prevent double-spend.
* `mapping(uint256 => address) public externalActionMap` — Maps external action IDs to contract addresses.
* `IHinkalHelper public hinkalHelper` — Helper contract for checks and fees.
* `IHinkalInLogic public hinkalInLogic` — Logic contract for complex flows.

**Functions**

_**createCommitment**_

```solidity
function createCommitment(UTXO memory utxo)
    internal view returns (OnChainCommitment memory)
```

Computes a Poseidon commitment:

* Fungible (`tokenId == 0`): Poseidon4(amount, erc20, stealthAddress, timestamp)
* NFT (`tokenId > 0`): Poseidon5(amount, erc20, stealthAddress, timestamp, tokenId)
* Returns `OnChainCommitment{ utxo, commitment }`.

A user action often results in new, private notes (UTXOs). Each note is converted into a field element commitment using Poseidon. For fungible tokens, the essence of the note is amount, token address, stealth address, and time. NFTs additionally bind `tokenId`, ensuring uniqueness. These commitments are what enter the Merkle tree as leaves, forming the cryptographic backbone of private ownership.

Why two arities?

* NFT commitments include `tokenId` to bind the unique asset to the note; fungible commitments omit it to reduce hash arity and gas. Both are deterministic and circuit-compatible.

_**insertCommitments**_

```solidity
function insertCommitments(
    uint256[][] memory outCommitments,
    bytes[][] memory encryptedOutputs,
    OnChainCommitment[] memory onChainCommitments,
    bool[] memory onChainCreation
) internal
```

* Flattens commitment leaves, inserts via `insertMany`, and emits events:
  * For off-chain commitments: `NewCommitment(commitment, +index, encryptedOutput)`
  * For on-chain commitments: `NewCommitment(commitment, -index, abi.encode(utxo))`

After a successful proof and execution, the protocol crystallizes newly created notes into the Merkle tree. Off-chain generated commitments (from the zk-circuit outputs) and on-chain generated commitments (derived here from `UTXO[]`) are flattened and inserted together to preserve ordering. Indexers learn not just the commitment values but also the provenance:

* Positive indices → off-chain provided commitments (encrypted outputs accompany them).
* Negative indices → on-chain constructed commitments, where the full `UTXO` is ABI-encoded in the event for transparency and recovery.

This dual-path insertion lets the protocol support both fully off-chain note creation and on-chain note creation paths (e.g., proofless deposits or DeFi integrations that mint notes in-process).

_**insertNullifiers**_

```solidity
function insertNullifiers(
    uint256[][] calldata inputNullifiers,
    bool[] calldata onChainCreation
) internal
```

Ensures each nullifier is unused, sets it used, and emits `Nullified(nullifier)`.

Every spend consumes one or more prior notes. In zero-knowledge, the spender proves knowledge of secrets; on-chain, we only see derived nullifiers. The act of inserting a nullifier is how the chain acknowledges a note as irrevocably spent. Reuse attempts revert, making double-spend attempts impossible within the same root set.

#### Security and Design Notes

* Double-spend prevention via `nullifiers` mapping and strict checks.
* Event-rich state updates for efficient indexing and monitoring.
* Access controls separate admin duties for helper vs. broader protocol admin.
* Uses `Transferer` helpers for robust ERC20/ETH/NFT handling and interface support.

Merkle implementation highlights:

* Batched insertion (`insertMany`) sorts leaves to minimize hashing and gas.
* Roots are circular-buffered to allow proofs against recent historical states.
* Poseidon hash functions are injected via constructor for flexibility and auditability.
