aiken_design_patterns/linked_list/nested
Two-level linked lists using one list NFT policy and one spend script. Datums represent three element kinds:
Rootanchors the outer list.InnerRootis a node in the outer list and the root of one inner list.Nodebelongs to an inner list beneath anInnerRootor anotherNode.
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:
Root: data.InnerRoot: key, data, and child link.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:
Root: spent data and continued data.InnerRoot: key, spent data, continued data, and preserved child link.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
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:
- Preserved element address.
- Change in Lovelace count from input to output.
- The preserved
Element.link field.
- A Church-style selector for
Root, InnerRoot, and Node updates.
- Reference script of the input element, if present.
- 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
Authenticate a nested-list element UTxO and expose its read-only info.
The callback receives:
- Element address.
- Lovelace quantity.
- The element’s outer or inner-list
Element.link.
- A Church-style selector for
Root, InnerRoot, and Node data.
- 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.
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.
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.
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:
- Preserved element address.
- Change in Lovelace count from input to output.
- The preserved
Element.linkfield. - A Church-style selector for
Root,InnerRoot, andNodeupdates. - Reference script of the input element, if present.
- 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.
Authenticate a nested-list element UTxO and expose its read-only info.
The callback receives:
- Element address.
- Lovelace quantity.
- The element’s outer or inner-list
Element.link. - A Church-style selector for
Root,InnerRoot, andNodedata. - 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.
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.
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.