aiken_design_patterns/linked_list/nested

Two-level linked lists using one list NFT policy and one spend script. Datums represent three element kinds:

  1. Root anchors the outer list.
  2. InnerRoot is a node in the outer list and the root of one inner list.
  3. Node belongs to an inner list beneath an InnerRoot or another Node.

Base linked-list authentication, NFT namespace, list spend script/payment credential, and mint-policy invariants still apply. The list policy, not the payment credential, authenticates list state. Anyone can create a UTxO at the list payment credential without invoking either script. A UTxO with no asset under the list policy is not a nested list element, requires no linked-list validation, and is ignored by the namespace-aware scanners even when it is at that credential. These helpers make no claim about its datum, value, or spendability. UTxOs at one credential are independent and transactions consume only explicitly selected inputs, so an outside party cannot force such a UTxO into or use it to block a list transition. The list never needs to collect or clean it up. Off-chain discovery must authenticate a reserved structural token and canonical element shape under the list policy, not infer state from address.

<list_nft_policy_id, root_key> is globally reserved as the singleton outer root asset. The one-time init is its only mint, its callback pins it to the canonical root at the list payment credential, all continuations keep it there, and deinit is its only burn. Every other policy branch must reject changes to root_key. A valid deployment therefore cannot contain this token at another payment credential; root-anchored deinit relies on that policy-wide invariant. Continued anchors are checked by full address equality; newly minted nested elements share the anchor payment credential. InnerRoot and Node NFTs share one node-key namespace: constructors distinguish datum roles, but keys must still be unique within that policy/prefix namespace and cannot be duplicated across roles. Nested insertion and deinit operations reuse the advanced namespace-aware input scanner. An authenticated continued anchor, or the canonical root input during deinit, supplies the list payment credential; every same-policy input at another credential is collected, without interpreting its asset names, for the final application callback. Every inputs argument passed to a nested helper must be the complete, unmodified ScriptContext.transaction.inputs list in ledger order. Do not pass a filtered, reordered, reconstructed, or redeemer-provided list; the scanner can only classify inputs it receives. Every Output passed to a nested mint helper must come from ScriptContext.transaction.outputs. The helpers validate selected outputs as nested linked-list UTxOs, but intentionally leave selection to callers so validators can use indexes, filtering, list.find, or another contract-specific method. Do not pass redeemer-provided or locally constructed outputs to these helpers.

Supported operations are init/deinit, insertion helpers, and non-structural data updates. Nested remove/read spend logic must be custom.

Types

Nested element datum.

Root and InnerRoot participate in the outer list, so Element.link points to the next outer element. Node participates in the inner list beneath an InnerRoot, so Element.link points to the next inner child. The constructor in ElementData determines which interpretation applies.

Constructors

  • Element { data: ElementData<root_data, inner_root_data, node_data>, link: Link }

Payload constructors for nested elements.

Root anchors the outer list. InnerRoot is both an outer-list element and the root of one inner list; its child_link points to the first child Node in that inner list. Node is an inner-list element and uses the enclosing Element.link as its next-child link.

Constructors

  • Root { data: root_data }
  • InnerRoot { data: inner_root_data, child_link: Link }
  • Node { data: node_data }

get_element_info continuation.

Args: Address, Lovelace, Element.link, a Church-style selector for constructor-specific data, and reference script.

Alias

ElementInfo<a> = fn(
    Address,
    Lovelace,
    Link,
    ElementInfoSelector<a>,
    Option<ScriptHash>,
  ) ->
    a

Church-style branch selector for callbacks that need constructor-specific information while reading a nested-list element.

Address, Lovelace, Element.link, and reference script are common to every constructor and are passed outside this selector by get_element_info. Only InnerRoot receives a link here, because only InnerRoot has the constructor-specific child_link.

The handlers receive:

  1. Root: data.
  2. InnerRoot: key, data, and child link.
  3. Node: key and data.

Alias

ElementInfoSelector<a> = fn(
    fn(Data) -> a,
    fn(NodeKey, Data, Link) -> a,
    fn(NodeKey, Data) -> a,
  ) ->
    a

Church-style branch selector for callbacks that need constructor-specific information during a nested-list data update.

Address and Element.link are common to every constructor and are passed outside this selector by spend_for_updating_elements_data. Only InnerRoot receives a link here, because only InnerRoot has a second, constructor-specific child_link.

The handlers receive:

  1. Root: spent data and continued data.
  2. InnerRoot: key, spent data, continued data, and preserved child link.
  3. Node: key, spent data, and continued data.

Alias

ElementUpdate<result> = fn(
    fn(Data, Data) -> result,
    fn(NodeKey, Data, Data, Link) -> result,
    fn(NodeKey, Data, Data) -> result,
  ) ->
    result

Raw Data nested-element alias used by authentication helpers.

This is not an application datum type. It lets the library authenticate and distinguish Root, InnerRoot, and Node before passing raw payload data to callbacks for application-specific decoding.

Alias

GenericElement = Element<Data, Data, Data>

Raw Data nested element-data alias used by authentication helpers.

The constructor identifies the structural role of the authenticated UTxO; the inner Data remains caller-defined and should be decoded inside callbacks.

Alias

GenericElementData = ElementData<Data, Data, Data>

Branch selector for callbacks that need anchor-specific information during inner-list insertion. Call anchor(on_inner_root, on_node); exactly one handler is invoked.

Root cannot anchor insert_into_inner_list; the previous inner-list link is provided separately as the new node’s link.

Alias

InnerListAnchor<result> = fn(OnInnerRootAnchor<result>, OnNodeAnchor<result>) ->
    result

Final callback after minting a whole inner list.

Args: anchor input, anchor Lovelace change, anchor key, spent anchor data, continued anchor data, spent anchor’s old outer link, final folded state, NonStructuralListAssets mint/burn changes, and NonStructuralListInputs. None key means root data; Some(key) means inner-root data.

Alias

InnerListFinalValidation<state> = fn(
    Input,
    LovelaceChange,
    Option<NodeKey>,
    Data,
    Data,
    Link,
    state,
    NonStructuralListAssets,
    NonStructuralListInputs,
  ) ->
    Bool

Callback for inserting one Node into an existing inner list.

The produced node address is not repeated in this callback; callers that need full-address checks can capture the new_node_output they passed to insert_into_inner_list.

Args: anchor input, anchor Lovelace change, anchor branch selector, new node Lovelace, new node key, new node data, new node link already checked to equal the anchor’s previous inner-list link, new node reference script, and NonStructuralListAssets mint/burn changes plus NonStructuralListInputs.

Alias

InnerListInsertValidation = fn(
    Input,
    LovelaceChange,
    InnerListAnchor<Bool>,
    Lovelace,
    NodeKey,
    Data,
    Link,
    Option<ScriptHash>,
    NonStructuralListAssets,
    NonStructuralListInputs,
  ) ->
    Bool

Per-node fold callback for minting a whole inner list.

Args: current state, zero-based node position within inner_node_outputs, node address, node Lovelace, node key, node data, node link, and node reference script.

Return the next fold state. Use expect or fail inside the callback to reject an invalid node.

Alias

InnerNodeValidation<state> = fn(
    state,
    Int,
    Address,
    Lovelace,
    NodeKey,
    Data,
    Link,
    Option<ScriptHash>,
  ) ->
    state

Inner-root-info continuation, including any reference script.

Args: Address, Lovelace, inner-root key, inner-root data, outer link, child link, reference script.

Alias

InnerRootElementInfo<a> = fn(
    Address,
    Lovelace,
    NodeKey,
    Data,
    Link,
    Link,
    Option<ScriptHash>,
  ) ->
    a

Initial state builder for minting a whole inner list.

The produced inner-root address is available from the new_inner_root_output supplied to insert_inner_list_ascending or insert_inner_list_descending.

Args: inner-root Lovelace, inner-root key, inner-root data, first-child link, and inner-root reference script.

Alias

InnerRootStatePreparation<state> = fn(
    Lovelace,
    NodeKey,
    Data,
    Link,
    Option<ScriptHash>,
  ) ->
    state

Ordering mode for produced node keys.

Ascending and Descending use lexicographic bytearray ordering over NodeKey bytes. Unordered skips key comparison but still enforces that produced links match the declared chain.

Constructors

  • Ascending
  • Descending
  • Unordered

Node-info continuation, including any reference script.

Args: Address, Lovelace, node key, node data, inner-list link, reference script.

Alias

NodeElementInfo<a> = fn(
    Address,
    Lovelace,
    NodeKey,
    Data,
    Link,
    Option<ScriptHash>,
  ) ->
    a

Handler for an InnerRoot anchor during inner-list insertion.

Arguments are the inner-root key, spent inner-root data, continued inner-root data, and the spent inner root’s previous first-child link. The continued child link and produced node wiring are already structurally checked before this handler runs.

Alias

OnInnerRootAnchor<result> = fn(NodeKey, Data, Data, Link) -> result

Handler for a Node anchor during inner-list insertion.

Arguments are the node key, spent node data, and continued node data. The old child successor and new successor wiring are passed separately through the main insertion callback.

Alias

OnNodeAnchor<result> = fn(NodeKey, Data, Data) -> result

Root-info continuation, including any reference script.

Args: Address, Lovelace, root data, root outer link, reference script.

Alias

RootElementInfo<a> = fn(Address, Lovelace, Data, Link, Option<ScriptHash>) -> a

Constants

spend_for_adding_or_removing_an_element: fn(PolicyId, Value) -> Bool

Re-export of the base structural spend gate.

This is not standalone authorization: it only checks that some list-policy mint/burn is present. The paired minting policy must prove the transition with a matching linked-list mint helper, or equivalent custom validation where no helper exists. Namespace-aware mint helpers must receive the complete transaction input list.

Functions

init(
  nonce_validated: Bool,
  produced_element_output: Output,
  tx_mint: Value,
  root_validator: fn(Address, Lovelace, Data, Option<ScriptHash>) -> Bool,
) -> RootEval<Bool>

Initialize a nested list root. nonce_validated must prove a unique consumed nonce or equivalent one-time authorization; the produced root may carry a reference script passed to root_validator, and the transaction mint must be exactly one root NFT under the list policy. Passing literal True is only appropriate in tests and fixtures.

This one-time initialization is the only policy branch allowed to mint <list_nft_policy_id, root_key>. It mints exactly one, and root_validator must pin the produced root to the intended list payment credential. No other policy branch may mint that reserved asset.

produced_element_output must be selected from ScriptContext.transaction.outputs. This helper authenticates the selected output as the produced root while leaving index/filter selection to the caller.

deinit(
  inputs: List<Input>,
  tx_mint: Value,
  root_validator: fn(
    Input,
    Lovelace,
    Data,
    NonStructuralListAssets,
    NonStructuralListInputs,
  ) ->
    Bool,
) -> ElementEval<Bool>

De-initialize an empty nested list.

The canonical root input establishes the authenticated list payment credential. Inputs without list-policy assets are ignored at every payment credential, including the root credential. Every additional same-policy input at that credential is rejected, while same-policy inputs at another credential are passed through without interpreting their names.

Locating the root by root_key relies on the policy-wide singleton-root invariant: only the one-time init can mint this asset, it remains at the canonical list credential, and this deinit is its only burn. A root_key token at another credential is unreachable under the required policy.

Deinitialization must burn the root NFT. Additional changes under the list policy are permitted only outside the root and node namespaces. The callback receives those changes separately from collected same-policy inputs and can inspect element_input.output.reference_script through the spent input.

inputs must be the complete, unmodified ScriptContext.transaction.inputs list in ledger order. This mint helper must be paired with aiken_design_patterns/linked_list.spend_for_adding_or_removing_an_element in the root spend branch.

insert_into_inner_list(
  ordering_of_inner_node_keys: ListOrdering,
  continued_anchor_element_output: Output,
  new_node_output: Output,
  inputs: List<Input>,
  tx_mint: Value,
  additional_validations: InnerListInsertValidation,
) -> ElementEval<Bool>

Insert a single Node into an existing inner list.

The anchor may be the InnerRoot for the first child or a Node for later positions. Finalize with base linked_list.run_element_with; mint a whole inner list separately and insert its InnerRoot into the outer list with insert_inner_list_ascending or insert_inner_list_descending. run_element_with is required so this helper can reject extra same-policy mints/burns of the reserved root key as well as the node namespace.

continued_anchor_element_output and new_node_output must both be selected from ScriptContext.transaction.outputs; this helper validates the selected outputs, not the caller’s selection method.

inputs must be the complete, unmodified ScriptContext.transaction.inputs list in ledger order.

insert_inner_list_ascending(
  ordering_of_inner_node_keys: ListOrdering,
  continued_anchor_element_output: Output,
  new_inner_root_output: Output,
  inputs: List<Input>,
  inner_node_outputs: List<Output>,
  tx_mint: Value,
  prepare_state: InnerRootStatePreparation<state>,
  node_validator: InnerNodeValidation<state>,
  final_validator: InnerListFinalValidation<state>,
) -> ElementEval<Bool>

Mint a whole inner list and insert its InnerRoot into the outer list in ascending order; ordering_of_inner_node_keys controls the child chain.

inner_node_outputs must follow new_inner_root.child_link, then each produced node link, ending at None. continued_anchor_element_output, new_inner_root_output, and every inner_node_outputs entry must be selected from ScriptContext.transaction.outputs. Callers decide whether that selection is by index, filtering, or another deterministic convention.

prepare_state, node_validator, and final_validator receive the application data, allowed extra same-policy changes, and collected inputs.

inputs must be the complete, unmodified ScriptContext.transaction.inputs list in ledger order.

insert_inner_list_descending(
  ordering_of_inner_node_keys: ListOrdering,
  continued_anchor_element_output: Output,
  new_inner_root_output: Output,
  inputs: List<Input>,
  inner_node_outputs: List<Output>,
  tx_mint: Value,
  prepare_state: InnerRootStatePreparation<state>,
  node_validator: InnerNodeValidation<state>,
  final_validator: InnerListFinalValidation<state>,
) -> ElementEval<Bool>

Descending variant of insert_inner_list_ascending: only the outer InnerRoot comparison changes to Greater; all other contract rules are identical, including the requirement that all supplied outputs come from ScriptContext.transaction.outputs and that inputs is the complete, unmodified ScriptContext.transaction.inputs list in ledger order.

Spending Script Helpers

spend_for_updating_elements_data(
  element_input_index: Int,
  continued_element_output_index: Int,
  element_input_outref: OutputReference,
  inputs: List<Input>,
  outputs: List<Output>,
  tx_mint: Value,
  additional_validations: fn(
    Address,
    LovelaceChange,
    Link,
    ElementUpdate<Bool>,
    Option<ScriptHash>,
    Option<ScriptHash>,
  ) ->
    Bool,
) -> ElementEval<Bool>

For spending an individual nested-list element and reproducing it with updated data, without affecting the structure of the nested list.

The continuation receives:

  1. Preserved element address.
  2. Change in Lovelace count from input to output.
  3. The preserved Element.link field.
  4. A Church-style selector for Root, InnerRoot, and Node updates.
  5. Reference script of the input element, if present.
  6. Reference script of the continued element, if present.

No list-policy mint/burn is allowed for a pure data update. inputs and outputs must be the complete, unmodified ScriptContext.transaction.inputs and ScriptContext.transaction.outputs lists in ledger order because the supplied indexes are resolved against them.

Exposed Helpers

get_element_info(
  element_utxo: Output,
  info_validations: ElementInfo<a>,
) -> ElementEval<a>

Authenticate a nested-list element UTxO and expose its read-only info.

The callback receives:

  1. Element address.
  2. Lovelace quantity.
  3. The element’s outer or inner-list Element.link.
  4. A Church-style selector for Root, InnerRoot, and Node data.
  5. Reference script of the element, if present.

Root elements must carry the configured root NFT. InnerRoot and Node elements must carry an NFT under the configured node namespace; the callback receives the stripped node key.

get_root_element_info(
  element_utxo: Output,
  info_validations: RootElementInfo<a>,
) -> RootEval<a>

Read root info through RootElementInfo.

The UTxO must carry the configured root NFT and a Root datum. The callback receives address, Lovelace, raw root payload data, the root outer link, and the reference script attached to the UTxO, if any.

get_inner_root_element_info(
  element_utxo: Output,
  info_validations: InnerRootElementInfo<a>,
) -> NodeEval<a>

Read inner-root info through InnerRootElementInfo.

The UTxO must carry a list NFT whose asset name starts with node_key_prefix and must contain an InnerRoot datum. The callback receives address, Lovelace, the inner-root key with the prefix stripped, raw inner-root payload data, the outer-list link, the inner child link, and the reference script attached to the UTxO, if any.

get_node_element_info(
  element_utxo: Output,
  info_validations: NodeElementInfo<a>,
) -> NodeEval<a>

Read node info through NodeElementInfo.

The UTxO must carry a list NFT whose asset name starts with node_key_prefix and must contain a Node datum. The callback receives address, Lovelace, the node key with the prefix stripped, raw node payload data, the inner-list link, and the reference script attached to the UTxO, if any.

Search Document