aiken_design_patterns/linked_list/advanced

Advanced linked-list helpers reuse the default module’s element type, aliases, runners, spend helper, and structural invariants.

The list policy is the authentication boundary. Anyone can create a UTxO at the list payment credential without invoking the spend script or list policy. A UTxO with no asset under the list policy is not list state, even at that credential; namespace-aware scanners ignore it completely and 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 root asset. A one-time init is its only permitted mint, the init callback pins it to the canonical root at the list payment credential, every continuation keeps it there, and deinit is its only permitted burn. Every other policy branch, including application-specific same-policy branches, must reject changes to root_key. A valid deployment therefore cannot contain this token at another payment credential. Root-anchored scanning relies on this policy-wide invariant.

Key differences from default:

  1. Reference-script-aware callbacks are available for init, structural node operations, reads, and updates. Continued structural anchors preserve their reference scripts.
  2. Structural node mint/burn helpers and deinit may accept additional same-policy mint/burn outside the root key and node-key namespace and expose it as NonStructuralListAssets. List element UTxOs remain exactly ADA plus one list NFT; root init and non-structural continuations remain strict. The same operations expose same-policy inputs found at other payment credentials as NonStructuralListInputs. Continued anchors or, for deinit, the canonical root input authenticate the list payment credential internally. Inputs without list-policy assets never enter either classification.
  3. Insert/remove callbacks receive both spent and continued anchor data; caller callbacks decide valid data changes while structural identity and links are enforced.
  4. Advanced structural mint/burn helpers still pair with the base spend-side helper aiken_design_patterns/linked_list.spend_for_adding_or_removing_an_element.
  5. Every Output passed to an advanced mint helper must come from ScriptContext.transaction.outputs. The helpers validate the selected outputs as linked-list UTxOs, but the caller chooses how to select them: indexes, filtering, list.find, or another contract-specific method are all valid as long as the Output is not redeemer data or a locally constructed value.
  6. Every inputs argument passed to a structural helper or namespace-aware scanner 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. Insert/remove callbacks share an anchor prefix: (anchor input, anchor Lovelace change, anchor key, spent anchor data, continued anchor data). anchor key is None for root data and Some(key) for node data.

Types

Callback for appending an unordered node.

The shared advanced anchor prefix is followed by the new terminal node’s Lovelace, key, data, reference script, and permitted same-policy mint/burn changes. The library has already checked that the anchor was terminal, the new node is terminal, and the continued anchor points to the new node. The final two arguments are NonStructuralListAssets from mint/burn changes and NonStructuralListInputs from consumed transaction inputs. This unordered operation does not enforce application-specific key uniqueness. The produced node address is available from the new_node_output supplied to the helper.

Alias

AppendValidation = fn(
    Input,
    LovelaceChange,
    Option<NodeKey>,
    Data,
    Data,
    Lovelace,
    NodeKey,
    NodeData,
    Option<ScriptHash>,
    NonStructuralListAssets,
    NonStructuralListInputs,
  ) ->
    Bool

Element-info continuation, including any reference script.

Args: Address, Lovelace, element key (None for root, Some(key) for node), data, link, reference script.

Alias

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

Callback for folding from the root.

The library has already selected the root by output reference, authenticated the folded node, proved that the root points to that node, preserved the continued root address/NFT/reference script, and moved the folded node’s old link onto the continued root.

Args: root input, root Lovelace change, spent root data, folding node input, folding node Lovelace, folding node key, folding node data, folding node link, continued root data, NonStructuralListAssets mint/burn changes, and NonStructuralListInputs.

Alias

FoldValidation = fn(
    Input,
    LovelaceChange,
    RootData,
    Input,
    Lovelace,
    NodeKey,
    NodeData,
    Link,
    RootData,
    NonStructuralListAssets,
    NonStructuralListInputs,
  ) ->
    Bool

Node-info continuation, including any reference script.

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

Alias

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

Type alias to represent other assets outside reserved root and node namespaces, under the list policy. These are not assets carried by list element UTxOs.

Positive quantities are mints; negative quantities are burns.

Alias

NonStructuralListAssets = Dict<AssetName, Int>

Non-structural inputs carrying assets under the list policy.

Each pair contains the unchanged input and the complete dictionary of its same-policy names and quantities. These inputs are outside the authenticated list payment credential; their asset names are not interpreted or restricted. Inputs are kept in transaction order; ADA and foreign-policy assets remain available through the Input.

UTxOs with no asset under the list policy are absent from this type and are ignored even at the list payment credential. The unclassified names in this type do not relax the policy-wide root invariant: a correctly wired policy can never place <list_nft_policy_id, root_key> at an external credential. The scanner relies on that lifetime invariant rather than re-proving the provenance of an already existing token.

This list is complete only when the helper received the complete, unmodified ScriptContext.transaction.inputs list in ledger order.

Alias

NonStructuralListInputs = Pairs<Input, NonStructuralListAssets>

Callback for ordered insertion.

The shared advanced anchor prefix is: (anchor input, anchor Lovelace change, anchor key, spent anchor data, continued anchor data), where anchor key is None for root anchors and Some(key) for node anchors.

After that prefix, this callback receives the authenticated new node Lovelace, key, data, link, reference script, and permitted same-policy mint/burn changes. Structural identity, link wiring, reference-script preservation on the continued anchor, minting, and key ordering have already been checked. The final two arguments are NonStructuralListAssets from mint/burn changes and NonStructuralListInputs from consumed transaction inputs. 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 the insertion helper.

Alias

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

Callback for prepending an unordered node.

The anchor has already been authenticated as the root, the continued root has been checked to point to the new node, and the new node has been checked to point to the root’s previous first node. The produced node address is available from the new_node_output supplied to the helper.

Args: root input, root Lovelace change, spent root data, continued root data, new node Lovelace, new node key, new node data, new node link, new node reference script, NonStructuralListAssets mint/burn changes, and NonStructuralListInputs.

Alias

PrependValidation = fn(
    Input,
    LovelaceChange,
    RootData,
    RootData,
    Lovelace,
    NodeKey,
    NodeData,
    Link,
    Option<ScriptHash>,
    NonStructuralListAssets,
    NonStructuralListInputs,
  ) ->
    Bool

Same-policy changes left after removing an expected structural NFT.

This intermediate value may still contain other structural asset names. Callers must process and validate the remainder before treating it as non-structural.

Alias

RemainingSamePolicyAssetChanges = Dict<AssetName, Int>

Callback for removal.

The shared advanced anchor prefix is followed by the authenticated removing node input, Lovelace, key, data, link, and permitted same-policy mint/burn changes. The library has already selected the anchor by output reference, proved that the anchor points to the removed node, preserved the continued anchor address/NFT/reference script, and checked that the continued link skips over the removed node. The final two arguments are NonStructuralListAssets from mint/burn changes and NonStructuralListInputs from consumed transaction inputs.

Alias

RemoveValidation = fn(
    Input,
    LovelaceChange,
    Option<NodeKey>,
    Data,
    Data,
    Input,
    Lovelace,
    NodeKey,
    NodeData,
    Link,
    NonStructuralListAssets,
    NonStructuralListInputs,
  ) ->
    Bool

Root-info continuation, including any reference script.

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

Alias

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

A structural list token classified once by the namespace-aware input scanner.

RootToken proves that the scanned asset name equals the configured root_key. NodeToken retains both the complete asset name and its prefix-stripped key because downstream operations need each representation. RootToken classification relies on the policy-wide invariant that no external root_key token can exist; matching the name alone does not re-prove the token’s lifetime provenance.

Constructors

  • RootToken
  • NodeToken { asset_name: AssetName, key: NodeKey }

Callback for non-structural element continuations.

The updater preserves address, NFT, constructor, and link; this callback controls the Lovelace change, data transition, and reference-script transition.

Args: preserved element address, Lovelace change, element key (None for root), old data, old reference script, new data, new reference script, preserved link.

Alias

UpdateValidation = fn(
    Address,
    LovelaceChange,
    Option<NodeKey>,
    Data,
    Option<ScriptHash>,
    Data,
    Option<ScriptHash>,
    Link,
  ) ->
    Bool

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 the matching linked-list mint helper. Namespace-aware mint helpers must receive the complete transaction input list.

Functions

validate_list_nft_and_get_remaining_same_policy_asset_changes(
  v: Value,
  expected_nft_policy: PolicyId,
  expected_nft_name: AssetName,
  expected_nft_qty: Int,
) -> RemainingSamePolicyAssetChanges

Validate the expected structural NFT mint/burn and return the remaining changes under the same policy.

validate_no_reserved_list_asset_changes(
  remaining_same_policy_asset_changes: RemainingSamePolicyAssetChanges,
  root_key: AssetName,
  node_key_prefix: AssetName,
  node_key_prefix_length: Int,
) -> Bool

Validates that additional same-policy asset changes do not use reserved list asset names. Applies to both mints and burns.

A remaining same-policy asset whose name starts with node_key_prefix would collide with the list node namespace. A remaining asset whose name equals root_key would collide with the list root NFT. Rejecting both here lets advanced callers allow unrelated same-policy assets without weakening the structural root/node-key invariants.

This validates only the branch that calls it. The policy-wide singleton-root invariant requires every other redeemer branch under the same policy to reject minting or burning root_key as well.

Initialization and De-initialization

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

Initialize the root while passing its reference script to the callback. No extra mint/burn changes under the list NFT policy are allowed.

nonce_validated must prove a unique consumed nonce or equivalent one-time authorization. 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.

root_validator receives the produced address, Lovelace quantity, raw root data, and optional reference script. The address is passed explicitly even though callers could inspect produced_element_output, so callbacks can validate the destination without destructuring the output again.

produced_element_output must be selected from ScriptContext.transaction.outputs. This helper authenticates that selected output as the produced root; it deliberately leaves index/filter selection to the surrounding validator.

It is up to callers to ensure this address has a script payment credential controlled by a script that only uses the spend helpers from this library. In other words, the destination script must ensure list NFTs never leave its custody.

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

Deinitialize an empty list while exposing non-structural same-policy mint/burn changes and inputs to the callback.

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 the root credential is rejected, while same-policy inputs at another credential are passed through without interpreting their asset 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 and are passed separately as NonStructuralListAssets.

root_validator receives the spent root input, root Lovelace, raw root data, permitted same-policy changes, and collected same-policy inputs. It can inspect root_input.output.reference_script through the input argument.

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.

Element Addition and Removal

insert_ascending(
  continued_anchor_element_output: Output,
  new_node_output: Output,
  inputs: List<Input>,
  tx_mint: Value,
  additional_validations: OrderedInsertValidation,
) -> ElementEval<Bool>

Insert a node into an ascending ordered list.

This is the advanced variant of the base module’s insert_ascending. It preserves the anchor address, NFT, reference script, constructor, and structural links, but passes both spent and continued anchor payload data to additional_validations. That lets the caller validate an application data transition while the library still enforces the linked-list structure.

Ordering rule:

  • root anchor: new < old_first
  • node anchor: anchor < new < old_next

The required new node NFT must be minted under the list policy. Additional same-policy mint/burn changes are allowed only outside the root key and node-key namespace, then passed to the callback as NonStructuralListAssets. Same-policy inputs at other payment credentials are passed separately as NonStructuralListInputs without interpreting their names.

inputs must be the complete, unmodified ScriptContext.transaction.inputs list in ledger order. Passing a filtered, reordered, reconstructed, or redeemer-provided list invalidates the scanner’s completeness guarantee.

continued_anchor_element_output and new_node_output must both be selected from ScriptContext.transaction.outputs. This helper validates the selected outputs and intentionally leaves their selection strategy to the caller.

insert_descending(
  continued_anchor_element_output: Output,
  new_node_output: Output,
  inputs: List<Input>,
  tx_mint: Value,
  additional_validations: OrderedInsertValidation,
) -> ElementEval<Bool>

Insert a node into a descending ordered list.

Same contract as insert_ascending, except the bytearray ordering is reversed:

  • root anchor: new > old_first
  • node anchor: anchor > new > old_next

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

continued_anchor_element_output and new_node_output must both come from ScriptContext.transaction.outputs; callers may select them by index, filtering, or another surrounding-validator convention.

append_unordered(
  continued_anchor_element_output: Output,
  new_node_output: Output,
  inputs: List<Input>,
  tx_mint: Value,
  additional_validations: AppendValidation,
) -> ElementEval<Bool>

Append a node to the terminal anchor of an unordered list.

The single authentic input is the current terminal anchor. The continued anchor links to the new node, and the produced node must be terminal. No key ordering or application-specific key uniqueness is checked.

The advanced callback receives spent and continued anchor data, the new node’s reference script, and any permitted same-policy mint/burn changes outside the root and node namespaces. It also receives every same-policy input spent from another payment credential.

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

continued_anchor_element_output and new_node_output must both be selected from ScriptContext.transaction.outputs. Output selection remains caller-controlled.

prepend_unordered(
  continued_root_element_output: Output,
  new_node_output: Output,
  inputs: List<Input>,
  tx_mint: Value,
  additional_validations: PrependValidation,
) -> ElementEval<Bool>

Prepend a node immediately after the root in an unordered list.

The single authentic input must be the root. The continued root points to the new node, and the new node points to the root’s previous first node. No key ordering is checked because this is an unordered operation.

The advanced callback receives spent and continued root data, the new node’s reference script, and permitted same-policy mint/burn changes outside the root and node namespaces. It also receives every same-policy input spent from another payment credential.

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

continued_root_element_output and new_node_output must both be selected from ScriptContext.transaction.outputs; this helper authenticates the selected outputs rather than proving the selection method.

remove(
  anchor_input_outref: OutputReference,
  continued_anchor_element_output: Output,
  inputs: List<Input>,
  tx_mint: Value,
  additional_validations: RemoveValidation,
) -> ElementEval<Bool>

Remove the node linked by the selected anchor.

anchor_input_outref identifies which of the two authentic list inputs is the anchor. The other authentic input must be the node referenced by the anchor’s current link. The continued anchor preserves address, NFT, and reference script, and its new link must skip over the removed node.

The removed node NFT must be burned. Additional same-policy burn/mint changes are allowed only outside the root key and node-key namespace, then passed to additional_validations for caller-specific checks. Same-policy inputs at other payment credentials are passed separately without interpreting their names.

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

Fold Functions

fold_from_root(
  anchor_root_input_outref: OutputReference,
  continued_anchor_root_output: Output,
  inputs: List<Input>,
  tx_mint: Value,
  additional_validations: FoldValidation,
) -> ElementEval<Bool>

Fold the root’s first node into the root.

anchor_root_input_outref must identify the root input. The second authentic input must be the node currently linked from the root. The continued root preserves address, NFT, and reference script, remains a Root, and adopts the folded node’s old link.

The folded node NFT must be burned. Additional same-policy burn/mint changes outside the root key and node-key namespace are passed to additional_validations. Same-policy inputs at other payment credentials are passed separately without interpreting their names.

inputs must be 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: UpdateValidation,
) -> ElementEval<Bool>

Continue one element without changing list structure.

This is the advanced variant of the base data-update helper. No list-policy mint/burn is allowed. The continuation must preserve address, NFT, constructor, and link. Unlike the base helper, the callback receives both the spent and continued reference scripts, so callers can decide whether a reference script is preserved, added, removed, or changed.

additional_validations receives:

  1. preserved element address
  2. Lovelace change
  3. element key (None for root, Some(key) for node)
  4. old data
  5. old reference script
  6. new data
  7. new reference script
  8. preserved link

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>

Read root or node info through ElementInfo.

The UTxO must be an authenticated singleton list element. The callback receives the element address, Lovelace quantity, None for root keys or Some(node_key) for node keys, with the node prefix stripped, plus the element payload, link, and optional reference script.

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 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 node link, and the reference script attached to the UTxO, if any.

Namespace-Aware Input Scanners

validate_root_anchored_singular_authentic_input(
  inputs: List<Input>,
  list_nft_policy_id: PolicyId,
  root_key: RootKey,
  node_key_prefix: NodeKeyPrefix,
  node_key_prefix_length: NodeKeyPrefixLength,
  return: fn(Input, Lovelace, Data, NonStructuralListInputs) -> Bool,
) -> Bool

Scan for exactly one canonical root input and derive the authenticated list payment credential from it.

Inputs without assets under the list policy are ignored at every credential. Same-policy inputs encountered before the root are deferred until its credential is known. A same-policy input at that credential must be canonical and advance the structural-input count; every same-policy input at another credential is collected without interpreting its asset names.

This scanner recognizes root_key before the list payment credential is known. It therefore relies on the policy-wide invariant that the one-time init minted exactly one root token into the canonical root, every continuation kept it there, and no other policy branch can mint it. Under that invariant an external root_key token cannot exist.

The continuation receives the root input, Lovelace, raw inline datum, and collected inputs in transaction order. inputs must be the complete, unmodified ScriptContext.transaction.inputs list in ledger order.

This is a low-level building block exposed for nested-module composition. Structural operation callers should use the operation helpers.

validate_singular_authentic_input(
  inputs: List<Input>,
  list_nft_policy_id: PolicyId,
  root_key: RootKey,
  node_key_prefix: NodeKeyPrefix,
  node_key_prefix_length: NodeKeyPrefixLength,
  list_payment_credential: PaymentCredential,
  return: fn(Input, Lovelace, StructuralToken, Data, NonStructuralListInputs) ->
    Bool,
) -> Bool

Scan for exactly one canonical structural input at the authenticated list payment credential.

Inputs without assets under the list policy are ignored at every credential, including the list credential. A same-policy input at the list credential must be canonical; every same-policy input at another credential is collected without interpreting its asset names. The continuation receives the classified StructuralToken, raw inline datum, and collected inputs in transaction order, so the calling operation can validate its own datum constructor and links.

inputs must be the complete, unmodified ScriptContext.transaction.inputs list in ledger order. A filtered, reordered, reconstructed, or redeemer-provided list makes both the exact structural-input count and collected-input result incomplete.

This is a low-level building block exposed for nested-module composition. Structural operation callers should use the operation helpers, which derive list_payment_credential from their continued anchor output.

validate_dual_authentic_inputs(
  anchor_input_outref: OutputReference,
  inputs: List<Input>,
  list_nft_policy_id: PolicyId,
  root_key: RootKey,
  node_key_prefix: NodeKeyPrefix,
  node_key_prefix_length: NodeKeyPrefixLength,
  list_payment_credential: PaymentCredential,
  return: fn(
    Input,
    Lovelace,
    StructuralToken,
    Data,
    Input,
    Lovelace,
    AssetName,
    NodeKey,
    Data,
    NonStructuralListInputs,
  ) ->
    Bool,
) -> Bool

Scan for exactly two canonical structural inputs at the authenticated list payment credential and select the anchor by output reference.

Canonical-value and same-policy-input collection rules are identical to validate_singular_authentic_input. The continuation receives the anchor as a StructuralToken. The other structural input is required to be a node and is passed as its complete asset name plus prefix-stripped key. Raw inline datum constructor and link checks remain the calling operation’s responsibility. The same complete, unmodified ScriptContext.transaction.inputs precondition applies.

This is a low-level building block exposed for variant-module composition. Structural operation callers should use the operation helpers instead.

Search Document