aiken_design_patterns/linked_list
Storing lists directly in datums is generally impractical: as the datum grows, the UTxO can become too expensive or even impossible to spend.
A linked list stores an unbounded collection as many UTxOs. Each list element is one UTxO with:
- some ADA,
- exactly one list NFT,
- an inline
Elementdatum, - and a link to its immediate successor.
The list NFT policy is the authentication boundary. A payment credential alone never proves that a UTxO is a list element: anyone can create an output at the list payment credential without invoking the spend script or list policy. A UTxO with no asset under the list policy is outside the list state, even at that credential. UTxOs sharing a credential are independent, and a transaction consumes only explicitly selected inputs; an outside party cannot force its UTxO into a list transition. Such UTxOs never need to be discovered, collected, spent, or cleaned up by the list protocol. These helpers do not authenticate or protect their data or value, and contracts must not rely on them to do so.
Usage Guideline
Contracts using this module should keep every authenticated list element controlled by one spend script/payment credential and one list NFT minting policy:
- Define the spend script datum as an applied alias of
Element<a, b>:
pub type Datum = linked_list.Element<RootType, NodeType>
- Ensure the UTxO produced by
initgoes to that spend script credential. - Implement list spend scripts so structural spends only use
spend_for_adding_or_removing_an_element, and non-structural updates usespend_for_updating_elements_data. - Implement the list minting policy so structural mint/burn branches only succeed through this module’s matching mint helpers for the exact init/insert/remove/fold/deinit shape. This spend/mint split is a deliberate budget compromise: the spend-side gate is not a standalone structural proof.
- Pass the complete, unmodified
ScriptContext.transaction.inputslist in ledger order to every helperinputsargument. A filtered, reordered, reconstructed, or redeemer-provided list invalidates exact-input claims. - Reserve
<list_nft_policy_id, root_key>across every policy branch. The one-time init is the only mint of this asset and creates exactly one at the list payment credential. Continuations preserve it there, deinit is its only burn, and every application-specific same-policy branch must reject it. A correctly wired policy therefore cannot have aroot_keytoken at an external payment credential. - Choose root and node NFT names so their namespaces are disjoint: use a
non-empty
node_key_prefix, non-empty node keys, and aroot_keythat does not begin with the node prefix.
These rules preserve the invariant that list NFTs cannot leave the list spend script/payment credential.
The guideline above implies a few important design constraints:
- Element UTxOs must not carry reference scripts. This default API rejects
them when authenticating outputs. Use
linked_list/advancedwhen reference scripts must be supported. - Continued anchors are checked by full address equality. Newly minted
nodes only need to share the anchor payment credential, which allows
callers to choose staking parts for new nodes. If a callback does not
receive a produced element address directly, the corresponding
Outputis an argument the caller supplied to the helper and can be captured by the callback. - Every
Outputpassed to a minting helper must be selected from the script context transaction outputs. The helpers authenticate those outputs as linked-list UTxOs, but they intentionally do not prove how the caller selected them. Callers may use redeemer indexes, filtering,list.find, or any other method, as long as the finalOutputvalue comes fromScriptContext.transaction.outputsand is not redeemer data or a locally constructed output. - The default mint helpers do not allow extra mint/burn changes under the
list NFT policy. Use
linked_list/advancedwhen structural operations also need unrelated same-policy assets outside the reserved root key and node-key namespace. - Inputs without assets under the list policy are not list elements and have no linked-list validity requirements, regardless of their payment credential. They cannot participate in or block a list transition unless the transaction builder explicitly selects them, and the list protocol never needs to collect or clean them up. Off-chain discovery must authenticate the structural list token and canonical element shape rather than treating every UTxO at the payment credential as list state. Namespace-aware advanced and nested scanners explicitly ignore inputs with no asset under the list policy.
- Root and node asset-name namespaces must be fixed and disjoint for the
deployed list. This is a soft precondition for budget reasons, not an
on-chain proof repeated by every branch. Agents wiring a contract must
choose a non-empty node prefix, non-empty node keys, and a root key that
cannot equal
node_key_prefix ++ node_key.
Types
Callback for appending an unordered node.
The anchor and the new node have already been checked to be terminal, and
the continued anchor has already been checked to link to the new node.
Since this is unordered, the library does not prove anything about the new
key beyond its node namespace membership; enforce application-level
uniqueness or indexing here if needed.
The produced node address is available from the new_node_output supplied
to the helper.
Args: anchor input, anchor Lovelace change, anchor key (None for root),
anchor data, new node Lovelace, new node key, new node data.
Alias
AppendValidation = fn(
Input,
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
) ->
Bool
Datum stored at each list UTxO: either the root or a node, plus its successor link.
Terminology:
Rootis the first element of the list.Nodeis any non-root list element.Elementis the umbrella term for either a root or a node.
Contracts using this library must define their datums as an applied datum alias:
pub type Datum = linked_list.Element<RootType, NodeType>
Where RootType and NodeType are application-specific custom types.
Constructors
-
Element { data: ElementData<root_data, node_data>, link: Link }
Root or node payload carried by an Element.
Operation callbacks usually provide the element key alongside the raw
payload data. None for the key means the data came from a Root;
Some(key) means it came from a Node.
Constructors
-
Root { data: root_data } -
Node { data: node_data }
Reader alias for helpers that can validate either root elements or node elements. These helpers need the list policy, the root asset name, and the node asset-name namespace.
To reduce execution budget, readers receive
node_key_prefix_lengthexplicitly next tonode_key_prefix; otherwise each operation would need to compute the length on-chain.
The length of
RootKeycan be at most 32 bytes. The length of anyNodeKeycan be at most(32 - NodeKeyPrefixLength). “Length” here means bytes after decoding from the chosen encoding, not the number of visible characters.
Alias
ElementEval<result> = fn(PolicyId, RootKey, NodeKeyPrefix, NodeKeyPrefixLength) ->
result
get_element_info continuation.
Args: Address, Lovelace, element key (None for root, Some(key) for node),
data, link.
Alias
ElementInfo<a> = fn(Address, Lovelace, Option<NodeKey>, Data, Link) -> a
Callback for folding the first node into the root.
The library has already authenticated the root, the first node, and the continued root; proved that the root points to the folded node; burned the folded node NFT; and moved the folded node’s old link onto the continued root. The callback decides whether the root data transition and folded node contents are valid for the application.
Args: root input, root Lovelace change, root data, folding node input, folding node Lovelace, folding node key, folding node data, folding node link, continued root data.
Alias
FoldValidation = fn(
Input,
LovelaceChange,
RootData,
Input,
Lovelace,
NodeKey,
NodeData,
Link,
RootData,
) ->
Bool
Raw Data element alias used by low-level authentication/read helpers.
This is not an application datum type. It is the authenticated structural
shape that lets the library decode Root versus Node before passing raw
payload data to caller callbacks.
Alias
GenericElement = Element<Data, Data>
Raw Data element-data alias used by low-level authentication/read helpers.
Root { data } and Node { data } identify the structural role of an
authenticated element, while the inner data remains caller-defined.
Alias
GenericElementData = ElementData<Data, Data>
Successor pointer stored in an element datum.
None means the element is terminal. Some(node_key) stores the successor
node key without NodeKeyPrefix; the policy/prefix namespace is supplied by
the operation context rather than repeated in each datum link.
Alias
Link = Option<NodeKey>
Lovelace delta for a continued list element.
This is always computed as:
continued_output_lovelace - spent_input_lovelace.
A positive value means the operation added ADA to the continued element, and a negative value means it removed ADA from it. The library does not impose an application policy on this delta; operation callbacks receive it so contracts can enforce their own deposit, fee, rent, or conservation rules.
Alias
LovelaceChange = Lovelace
Raw payload stored in a Node datum.
This has the same generic callback contract as RootData: the library
authenticates the list element and its structural role, while the caller
decides how to decode and validate the application payload.
Alias
NodeData = Data
get_node_element_info continuation.
Args: Address, Lovelace, node key, node data, link.
Alias
NodeElementInfo<a> = fn(Address, Lovelace, NodeKey, NodeData, Link) -> a
Reader alias for helpers that only need the list policy and node namespace.
Use this for node-only operations where the root key is irrelevant. These helpers do not validate the root asset name, so passing only the node prefix and prefix length keeps the caller surface narrower and avoids unused constants in mint/spend branches.
Alias
NodeEval<result> = fn(PolicyId, NodeKeyPrefix, NodeKeyPrefixLength) -> result
Key bytes of a linked-list node, without NodeKeyPrefix.
Node keys should be non-empty and unique under the list policy/prefix. A common approach is to derive the key from an input output reference or some other unique witness. If you allow duplicate keys, list integrity must be preserved by some other invariant.
Ordered operations compare keys as bytearrays. That means integer-like keys should be encoded with a fixed width if numeric ordering matters.
255 < 256, but#"ff" > #"0100"in bytearray comparison. Fixed-width encoding gives the intended order:#"00ff" < #"0100".
Alias
NodeKey = ByteArray
Bytes prefixing every node NFT asset name.
The full node asset name is node_key_prefix ++ node_key, and Cardano
asset names can be at most 32 bytes. For example, if the prefix takes 4
bytes, node keys can take at most 28 bytes.
The prefix should be non-empty and must not prefix RootKey; otherwise
root/node identity can collide.
Alias
NodeKeyPrefix = ByteArray
Must equal bytearray.length(node_key_prefix).
This is passed separately to avoid recomputing the prefix length on-chain.
Define it as a const so the compiler can inline it:
pub const node_key_prefix = "🔗"
pub const node_key_prefix_length =
bytearray.length(node_key_prefix)
Alias
NodeKeyPrefixLength = Int
Callback for ordered insertion.
The library has already authenticated the anchor input, authenticated the
continued anchor output and new node output, checked the anchor continuation,
checked the node-key namespace, checked minting, and checked the requested
key ordering before this callback runs.
The produced node address is not repeated in this callback; callers that
need stake-credential or full-address checks can capture the new_node_output
they passed to the insertion helper.
Args: anchor input, anchor Lovelace change, anchor key (None for root),
anchor data, new node Lovelace, new node key, new node data, new node link.
Return True to accept the application-specific part of the insertion; use
expect or return False to reject.
Alias
OrderedInsertValidation = fn(
Input,
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool
Callback for prepending an unordered node.
The anchor has already been authenticated as the root. The continued root
links to the new node, and the new node links to the root’s previous first
node. The callback is responsible only for application-specific root/new
node payload and Lovelace checks.
The produced node address is available from the new_node_output supplied
to the helper.
Args: root input, root Lovelace change, root data, new node Lovelace, new node key, new node data, new node link.
Alias
PrependValidation = fn(
Input,
LovelaceChange,
RootData,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool
Callback for removal.
The library has already selected the anchor by anchor_input_outref,
authenticated the removed node, proved that the anchor currently points to
that node, burned the removed node NFT, and checked that the continued
anchor skips over the removed node. The callback validates the
application-specific consequences of that removal.
Args: anchor input, anchor Lovelace change, anchor key (None for root),
anchor data, removing node input, removing node Lovelace, removing node key,
removing node data, removing node link.
Alias
RemoveValidation = fn(
Input,
LovelaceChange,
Option<NodeKey>,
Data,
Input,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool
Raw payload stored in a Root datum.
Operation callbacks receive raw Data rather than an application-specific
type so this module can remain generic. Callers should decode with expect
inside callbacks and let invalid application data reject the transaction.
Alias
RootData = Data
get_root_element_info continuation.
Args: Address, Lovelace, root data, link.
Alias
RootElementInfo<a> = fn(Address, Lovelace, RootData, Link) -> a
Reader alias for helpers that only need the list policy and root asset name.
Use this for root-only operations such as initialization, deinitialization, or root UTxO reads. These helpers do not validate node keys, so they do not need node namespace constants.
Alias
RootEval<result> = fn(PolicyId, RootKey) -> result
Asset name of the NFT held by the root element. It may be empty and can be no longer than 32 bytes.
Choose this outside the node namespace: it must not begin with
NodeKeyPrefix. The usual low-budget setup is an empty root key, a
non-empty node prefix, and non-empty node keys.
The asset <list_nft_policy_id, root_key> is a policy-wide singleton. A
correctly wired policy permits its one-time mint only through init, keeps
it in the canonical root at the list payment credential through every
continuation, and permits its burn only through deinit. Every other policy
branch must reject this asset name. Consequently, a root_key token at an
external payment credential is unreachable valid state; helpers which locate
the root by this asset rely on that invariant.
Some visible UTF-8 characters occupy more than one byte. For example, the tree emoji (🌳) takes up 4 bytes, so
"🌳"is not a 1-byte asset name.
Alias
RootKey = AssetName
Callback for non-structural element continuations.
The updater preserves address, NFT, constructor, and link; this callback controls the Lovelace change and data transition.
Args: Address, Lovelace change, element key (None for root), old data, new
data, preserved link. Return True to accept the application-specific update.
Alias
UpdateValidation = fn(
Address,
LovelaceChange,
Option<NodeKey>,
Data,
Data,
Link,
) ->
Bool
Functions
Finalization Functions
run_element_with(
reader: ElementEval<a>,
list_nft_policy_id: PolicyId,
root_key: RootKey,
node_key_prefix: NodeKeyPrefix,
node_key_prefix_length: NodeKeyPrefixLength,
) -> a
Finalize an ElementEval by supplying the list policy, root key, and node
namespace constants.
In most validators, this becomes a small helper:
use aiken_design_patterns/linked_list
const root_key = #""
const node_key_prefix = "NODE"
const node_key_prefix_length =
bytearray.length(node_key_prefix)
pub fn finalize_linked_list(
eval: linked_list.ElementEval<Bool>,
list_nft_policy_id: PolicyId,
) -> Bool {
linked_list.run_element_with(
eval,
list_nft_policy_id,
root_key,
node_key_prefix,
node_key_prefix_length,
)
}
Then your minting policy can validate an operation like this:
let linked_list_eval = {
let
anchor_input,
anchor_lovelace_change,
// etc.
<-
linked_list.insert_ascending(...)
expect anchor_lovelace_change >= 0
// other validations
}
expect linked_list_eval |> finalize_linked_list(own_policy_id)
If a redeemer branch can only handle a root or only handle a node, use
run_root_with or run_node_with with
the narrower set of constants.
Finalize a RootEval by supplying the list policy and root key.
Root-only helpers return this reader shape when they do not need the node
namespace. Keeping those constants out of the helper avoids requiring every
root branch to know the node prefix.
run_node_with(
reader: NodeEval<a>,
list_nft_policy_id: PolicyId,
node_key_prefix: NodeKeyPrefix,
node_key_prefix_length: NodeKeyPrefixLength,
) -> a
Finalize a NodeEval by supplying the list policy and node namespace
constants.
Node-only helpers return this reader shape when they never inspect or
validate the root key. The caller is still responsible for supplying the
exact prefix length that corresponds to node_key_prefix.
Initialization and De-initialization
init(
nonce_validated: Bool,
produced_element_output: Output,
tx_mint: Value,
root_validator: fn(Address, Lovelace, Data) -> Bool,
) -> RootEval<Bool>
Initialize a linked-list root UTxO with the root NFT and Root datum. The
output must contain exactly ADA plus that one root NFT.
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 must be 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, and raw
root data. 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 an output selected from
ScriptContext.transaction.outputs. This helper authenticates the selected
output as a singleton root UTxO, but leaves the selection method to the
caller so contracts can use indexes, filtering, or any other shape that fits
their redeemer/API.
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) -> Bool,
) -> RootEval<Bool>
Deinitialize an empty list.
root_validator args: spent root input, root Lovelace, raw root data.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The policy-wide
singleton-root invariant guarantees that the root_key asset being burned
cannot have originated at another payment credential.
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 new node into an ascending ordered list.
The anchor element is inferred from inputs by requiring exactly one
authentic linked-list element input. Its continued output must preserve the
spent anchor address and data, and the new node must use the anchor payment
credential.
Ordering rule:
- root anchor:
new < old_first
- node anchor:
anchor < new < old_next
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
None for root, Some(key) for node)
- anchor data
- new node Lovelace
- new node key
- new node data
- new node link
The default module preserves anchor data. Use linked_list/advanced when
insertion should allow caller-validated anchor data changes.
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs. The caller controls how
they are selected, for example by redeemer indexes or by filtering outputs.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
insert_descending(
continued_anchor_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: OrderedInsertValidation,
) -> ElementEval<Bool>
Like insert_ascending, but the ordering is reversed:
- root anchor:
new > old_first
- node anchor:
anchor > new > old_next
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs; this helper validates the
selected outputs, not the selection method.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
append_unordered(
continued_anchor_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: AppendValidation,
) -> ElementEval<Bool>
Append a new terminal node to an unordered list.
The single authentic input is the current terminal anchor. The continued
anchor links to the new node, and the new node must be terminal too.
There is no key-order comparison for unordered appends; callers are still
responsible for maintaining any application-specific uniqueness or indexing
invariant in additional_validations.
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
None for root, Some(key) for node)
- anchor data
- new node Lovelace
- new node key
- new node data
The default module preserves anchor data. Use linked_list/advanced when
appending should allow caller-validated anchor data changes.
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs. The caller may choose any
deterministic selection method that suits the surrounding validator.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
prepend_unordered(
continued_root_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: PrependValidation,
) -> ElementEval<Bool>
Prepend a new first node to an unordered list.
The single authentic input must be the root. The continued root links to the
new node, and the new node links to the root’s old first node.
additional_validations receives:
- root input
- root Lovelace change
- root data
- new node Lovelace
- new node key
- new node data
- new node link
The default module preserves root data. Use linked_list/advanced when
prepending should allow caller-validated root data changes.
continued_root_element_output and new_node_output must both be selected
from ScriptContext.transaction.outputs. The helper authenticates the
selected outputs and leaves indexing/filtering strategy to the caller.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
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.
The anchor is identified by anchor_input_outref; the anchor and removing
node are inferred from the transaction inputs. The removed node must be the
node named by the selected anchor’s current link, and the continued anchor’s
link becomes the removed node’s old link.
Only UTxOs authenticated by assets under the list policy participate in this
transition. Other UTxOs at the anchor payment credential are independent and
never need to be selected, spent, or cleaned up for removal.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
None for root, Some(key) for node)
- anchor data
- removing node input
- removing node Lovelace
- removing node key
- removing node data
- removing node link
The default module preserves anchor data. Use linked_list/advanced when
removal should allow caller-validated anchor data changes.
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.
The root is identified by its output reference. The helper authenticates the
root, the linked node, and the continued root; preserves the root
address/key; burns the folding node NFT; and enforces the root/node
namespace checks.
This traverses inputs and expects exactly two authentic list inputs at the
list payment credential: the root input and the folding node input.
UTxOs without an asset under the list policy are independent of that state
and never need to be selected, spent, or cleaned up for the fold.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
additional_validations receives:
- root input
- root Lovelace change
- root data
- folding node input
- folding node Lovelace
- folding node key
- folding node data
- folding node link
- continued root data
Spending Script Helpers
Spend-side gate for structural operations.
This function is intentionally not a structural validator by itself. It
must only be used in a spend script whose paired list minting policy accepts
structural mint/burns exclusively through this module’s matching mint
helpers (init, insert_*, remove, fold_from_root, deinit, etc.).
The security invariant is split across scripts:
- the spend script uses this gate to permit structural list spends only when
a list-policy mint/burn is present;
- the minting policy proves the exact linked-list transition with the
corresponding mint helper.
Do not use this as standalone authorization for moving a list UTxO.
A UTxO at the same payment credential without an asset under the list policy
is not a list UTxO. This gate deliberately provides no list-state guarantees
for it. Such a UTxO is independent, cannot be forced into another party’s
transaction, and never needs to be consumed or cleaned up by the list.
Use this for spend branches that allow adding/removing list elements. Use
spend_for_updating_elements_data for
non-structural continuations where the element is reproduced and no
list-policy mint/burn occurs.
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 an individual element without changing the linked-list structure.
No list-policy mint/burn is allowed; address, NFT, constructor, and link are
preserved before UpdateValidation decides the Lovelace and data change.
additional_validations receives:
- preserved element address
- Lovelace change
- element key (
None for root, Some(key) for node)
- old data
- new data
- preserved link
inputs and outputs must be the complete, unmodified
ScriptContext.transaction.inputs and ScriptContext.transaction.outputs
lists in ledger order. The indexes are interpreted against those lists.
Exposed Helpers
Authenticate a root or node UTxO and pass its info to info_validations.
The UTxO must be an inline-datum singleton list element under the supplied
policy. The returned key is None for the root and Some(node_key) for a
node, where node_key excludes NodeKeyPrefix. Root keys and node-prefix
membership are checked before the callback runs.
Authenticate a root UTxO and pass its info to info_validations.
The UTxO must carry the configured root NFT and a Root datum. The callback
receives address, Lovelace, raw root payload data, and the root link. Use
this when a script only accepts roots and should reject node UTxOs
immediately.
Authenticate a node UTxO and pass its info to info_validations.
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, the node key with the prefix stripped, raw node payload data, and
the node link.
Low-level Helpers
validate_singular_authentic_input(
inputs: List<Input>,
nft_policy_id: PolicyId,
return: fn(Input, Lovelace, AssetName, GenericElementData, Link) -> Bool,
) -> Bool
Validate exactly one authentic list input while scanning all transaction
inputs.
Inputs with no asset under nft_policy_id are not list candidates and are
assigned no structural meaning, irrespective of payment credential. After
one candidate is found, this optimized scanner rejects a second singleton
list-shaped input under the same policy; it assigns no meaning to inputs
without an asset under that policy.
The narrow candidate check relies on authentic elements containing exactly
ADA plus one list NFT and on ledger values listing ADA first. Other value
shapes are not canonical base-list elements and are ignored.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-one claim
is only as complete as the supplied list.
Args: callback receives input, Lovelace, asset name, data, link.
Another internal helper, you probably don’t need this.
validate_dual_authentic_inputs(
anchor_input_outref: OutputReference,
inputs: List<Input>,
nft_policy_id: PolicyId,
with: fn(
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
) ->
Bool,
) -> Bool
Validate exactly two authentic list inputs while scanning all transaction
inputs.
This compact fold is specialized for the exact two-list-input shape at one
payment credential; it is not a general-purpose transaction-input
classifier. This specialization has no bearing on list membership, which is
authenticated only by assets under nft_policy_id.
anchor_input_outref selects which found input is passed to the callback as
the anchor. Validation fails if neither authentic input has that reference.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-two claim
is only as complete as the supplied list.
Args: callback receives anchor input, anchor Lovelace, anchor asset name,
anchor data, anchor link, removing input, removing Lovelace, removing asset
name, removing data, removing link.
Another internal helper, you probably don’t need this.
run_element_with(
reader: ElementEval<a>,
list_nft_policy_id: PolicyId,
root_key: RootKey,
node_key_prefix: NodeKeyPrefix,
node_key_prefix_length: NodeKeyPrefixLength,
) -> a
Finalize an ElementEval by supplying the list policy, root key, and node
namespace constants.
In most validators, this becomes a small helper:
use aiken_design_patterns/linked_list
const root_key = #""
const node_key_prefix = "NODE"
const node_key_prefix_length =
bytearray.length(node_key_prefix)
pub fn finalize_linked_list(
eval: linked_list.ElementEval<Bool>,
list_nft_policy_id: PolicyId,
) -> Bool {
linked_list.run_element_with(
eval,
list_nft_policy_id,
root_key,
node_key_prefix,
node_key_prefix_length,
)
}
Then your minting policy can validate an operation like this:
let linked_list_eval = {
let
anchor_input,
anchor_lovelace_change,
// etc.
<-
linked_list.insert_ascending(...)
expect anchor_lovelace_change >= 0
// other validations
}
expect linked_list_eval |> finalize_linked_list(own_policy_id)
If a redeemer branch can only handle a root or only handle a node, use
run_root_with or run_node_with with
the narrower set of constants.
Finalize a RootEval by supplying the list policy and root key.
Root-only helpers return this reader shape when they do not need the node namespace. Keeping those constants out of the helper avoids requiring every root branch to know the node prefix.
run_node_with(
reader: NodeEval<a>,
list_nft_policy_id: PolicyId,
node_key_prefix: NodeKeyPrefix,
node_key_prefix_length: NodeKeyPrefixLength,
) -> a
Finalize a NodeEval by supplying the list policy and node namespace
constants.
Node-only helpers return this reader shape when they never inspect or
validate the root key. The caller is still responsible for supplying the
exact prefix length that corresponds to node_key_prefix.
init(
nonce_validated: Bool,
produced_element_output: Output,
tx_mint: Value,
root_validator: fn(Address, Lovelace, Data) -> Bool,
) -> RootEval<Bool>
Initialize a linked-list root UTxO with the root NFT and Root datum. The
output must contain exactly ADA plus that one root NFT.
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 must be 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, and raw
root data. 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 an output selected from
ScriptContext.transaction.outputs. This helper authenticates the selected
output as a singleton root UTxO, but leaves the selection method to the
caller so contracts can use indexes, filtering, or any other shape that fits
their redeemer/API.
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) -> Bool,
) -> RootEval<Bool>
Deinitialize an empty list.
root_validator args: spent root input, root Lovelace, raw root data.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The policy-wide
singleton-root invariant guarantees that the root_key asset being burned
cannot have originated at another payment credential.
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 new node into an ascending ordered list.
The anchor element is inferred from inputs by requiring exactly one
authentic linked-list element input. Its continued output must preserve the
spent anchor address and data, and the new node must use the anchor payment
credential.
Ordering rule:
- root anchor:
new < old_first
- node anchor:
anchor < new < old_next
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
None for root, Some(key) for node)
- anchor data
- new node Lovelace
- new node key
- new node data
- new node link
The default module preserves anchor data. Use linked_list/advanced when
insertion should allow caller-validated anchor data changes.
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs. The caller controls how
they are selected, for example by redeemer indexes or by filtering outputs.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
insert_descending(
continued_anchor_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: OrderedInsertValidation,
) -> ElementEval<Bool>
Like insert_ascending, but the ordering is reversed:
- root anchor:
new > old_first
- node anchor:
anchor > new > old_next
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs; this helper validates the
selected outputs, not the selection method.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
append_unordered(
continued_anchor_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: AppendValidation,
) -> ElementEval<Bool>
Append a new terminal node to an unordered list.
The single authentic input is the current terminal anchor. The continued
anchor links to the new node, and the new node must be terminal too.
There is no key-order comparison for unordered appends; callers are still
responsible for maintaining any application-specific uniqueness or indexing
invariant in additional_validations.
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
None for root, Some(key) for node)
- anchor data
- new node Lovelace
- new node key
- new node data
The default module preserves anchor data. Use linked_list/advanced when
appending should allow caller-validated anchor data changes.
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs. The caller may choose any
deterministic selection method that suits the surrounding validator.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
prepend_unordered(
continued_root_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: PrependValidation,
) -> ElementEval<Bool>
Prepend a new first node to an unordered list.
The single authentic input must be the root. The continued root links to the
new node, and the new node links to the root’s old first node.
additional_validations receives:
- root input
- root Lovelace change
- root data
- new node Lovelace
- new node key
- new node data
- new node link
The default module preserves root data. Use linked_list/advanced when
prepending should allow caller-validated root data changes.
continued_root_element_output and new_node_output must both be selected
from ScriptContext.transaction.outputs. The helper authenticates the
selected outputs and leaves indexing/filtering strategy to the caller.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
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.
The anchor is identified by anchor_input_outref; the anchor and removing
node are inferred from the transaction inputs. The removed node must be the
node named by the selected anchor’s current link, and the continued anchor’s
link becomes the removed node’s old link.
Only UTxOs authenticated by assets under the list policy participate in this
transition. Other UTxOs at the anchor payment credential are independent and
never need to be selected, spent, or cleaned up for removal.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
None for root, Some(key) for node)
- anchor data
- removing node input
- removing node Lovelace
- removing node key
- removing node data
- removing node link
The default module preserves anchor data. Use linked_list/advanced when
removal should allow caller-validated anchor data changes.
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.
The root is identified by its output reference. The helper authenticates the
root, the linked node, and the continued root; preserves the root
address/key; burns the folding node NFT; and enforces the root/node
namespace checks.
This traverses inputs and expects exactly two authentic list inputs at the
list payment credential: the root input and the folding node input.
UTxOs without an asset under the list policy are independent of that state
and never need to be selected, spent, or cleaned up for the fold.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
additional_validations receives:
- root input
- root Lovelace change
- root data
- folding node input
- folding node Lovelace
- folding node key
- folding node data
- folding node link
- continued root data
Spending Script Helpers
Spend-side gate for structural operations.
This function is intentionally not a structural validator by itself. It
must only be used in a spend script whose paired list minting policy accepts
structural mint/burns exclusively through this module’s matching mint
helpers (init, insert_*, remove, fold_from_root, deinit, etc.).
The security invariant is split across scripts:
- the spend script uses this gate to permit structural list spends only when
a list-policy mint/burn is present;
- the minting policy proves the exact linked-list transition with the
corresponding mint helper.
Do not use this as standalone authorization for moving a list UTxO.
A UTxO at the same payment credential without an asset under the list policy
is not a list UTxO. This gate deliberately provides no list-state guarantees
for it. Such a UTxO is independent, cannot be forced into another party’s
transaction, and never needs to be consumed or cleaned up by the list.
Use this for spend branches that allow adding/removing list elements. Use
spend_for_updating_elements_data for
non-structural continuations where the element is reproduced and no
list-policy mint/burn occurs.
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 an individual element without changing the linked-list structure.
No list-policy mint/burn is allowed; address, NFT, constructor, and link are
preserved before UpdateValidation decides the Lovelace and data change.
additional_validations receives:
- preserved element address
- Lovelace change
- element key (
None for root, Some(key) for node)
- old data
- new data
- preserved link
inputs and outputs must be the complete, unmodified
ScriptContext.transaction.inputs and ScriptContext.transaction.outputs
lists in ledger order. The indexes are interpreted against those lists.
Exposed Helpers
Authenticate a root or node UTxO and pass its info to info_validations.
The UTxO must be an inline-datum singleton list element under the supplied
policy. The returned key is None for the root and Some(node_key) for a
node, where node_key excludes NodeKeyPrefix. Root keys and node-prefix
membership are checked before the callback runs.
Authenticate a root UTxO and pass its info to info_validations.
The UTxO must carry the configured root NFT and a Root datum. The callback
receives address, Lovelace, raw root payload data, and the root link. Use
this when a script only accepts roots and should reject node UTxOs
immediately.
Authenticate a node UTxO and pass its info to info_validations.
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, the node key with the prefix stripped, raw node payload data, and
the node link.
Low-level Helpers
validate_singular_authentic_input(
inputs: List<Input>,
nft_policy_id: PolicyId,
return: fn(Input, Lovelace, AssetName, GenericElementData, Link) -> Bool,
) -> Bool
Validate exactly one authentic list input while scanning all transaction
inputs.
Inputs with no asset under nft_policy_id are not list candidates and are
assigned no structural meaning, irrespective of payment credential. After
one candidate is found, this optimized scanner rejects a second singleton
list-shaped input under the same policy; it assigns no meaning to inputs
without an asset under that policy.
The narrow candidate check relies on authentic elements containing exactly
ADA plus one list NFT and on ledger values listing ADA first. Other value
shapes are not canonical base-list elements and are ignored.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-one claim
is only as complete as the supplied list.
Args: callback receives input, Lovelace, asset name, data, link.
Another internal helper, you probably don’t need this.
validate_dual_authentic_inputs(
anchor_input_outref: OutputReference,
inputs: List<Input>,
nft_policy_id: PolicyId,
with: fn(
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
) ->
Bool,
) -> Bool
Validate exactly two authentic list inputs while scanning all transaction
inputs.
This compact fold is specialized for the exact two-list-input shape at one
payment credential; it is not a general-purpose transaction-input
classifier. This specialization has no bearing on list membership, which is
authenticated only by assets under nft_policy_id.
anchor_input_outref selects which found input is passed to the callback as
the anchor. Validation fails if neither authentic input has that reference.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-two claim
is only as complete as the supplied list.
Args: callback receives anchor input, anchor Lovelace, anchor asset name,
anchor data, anchor link, removing input, removing Lovelace, removing asset
name, removing data, removing link.
Another internal helper, you probably don’t need this.
insert_ascending(
continued_anchor_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: OrderedInsertValidation,
) -> ElementEval<Bool>
Insert a new node into an ascending ordered list.
The anchor element is inferred from inputs by requiring exactly one
authentic linked-list element input. Its continued output must preserve the
spent anchor address and data, and the new node must use the anchor payment
credential.
Ordering rule:
- root anchor:
new < old_first - node anchor:
anchor < new < old_next
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
Nonefor root,Some(key)for node) - anchor data
- new node Lovelace
- new node key
- new node data
- new node link
The default module preserves anchor data. Use linked_list/advanced when
insertion should allow caller-validated anchor data changes.
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs. The caller controls how
they are selected, for example by redeemer indexes or by filtering outputs.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
insert_descending(
continued_anchor_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: OrderedInsertValidation,
) -> ElementEval<Bool>
Like insert_ascending, but the ordering is reversed:
- root anchor:
new > old_first - node anchor:
anchor > new > old_next
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs; this helper validates the
selected outputs, not the selection method.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
append_unordered(
continued_anchor_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: AppendValidation,
) -> ElementEval<Bool>
Append a new terminal node to an unordered list.
The single authentic input is the current terminal anchor. The continued anchor links to the new node, and the new node must be terminal too.
There is no key-order comparison for unordered appends; callers are still
responsible for maintaining any application-specific uniqueness or indexing
invariant in additional_validations.
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
Nonefor root,Some(key)for node) - anchor data
- new node Lovelace
- new node key
- new node data
The default module preserves anchor data. Use linked_list/advanced when
appending should allow caller-validated anchor data changes.
continued_anchor_element_output and new_node_output must both be
selected from ScriptContext.transaction.outputs. The caller may choose any
deterministic selection method that suits the surrounding validator.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
prepend_unordered(
continued_root_element_output: Output,
new_node_output: Output,
inputs: List<Input>,
tx_mint: Value,
additional_validations: PrependValidation,
) -> ElementEval<Bool>
Prepend a new first node to an unordered list.
The single authentic input must be the root. The continued root links to the new node, and the new node links to the root’s old first node.
additional_validations receives:
- root input
- root Lovelace change
- root data
- new node Lovelace
- new node key
- new node data
- new node link
The default module preserves root data. Use linked_list/advanced when
prepending should allow caller-validated root data changes.
continued_root_element_output and new_node_output must both be selected
from ScriptContext.transaction.outputs. The helper authenticates the
selected outputs and leaves indexing/filtering strategy to the caller.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
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.
The anchor is identified by anchor_input_outref; the anchor and removing
node are inferred from the transaction inputs. The removed node must be the
node named by the selected anchor’s current link, and the continued anchor’s
link becomes the removed node’s old link.
Only UTxOs authenticated by assets under the list policy participate in this transition. Other UTxOs at the anchor payment credential are independent and never need to be selected, spent, or cleaned up for removal.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
additional_validations receives:
- anchor input
- anchor Lovelace change
- anchor key (
Nonefor root,Some(key)for node) - anchor data
- removing node input
- removing node Lovelace
- removing node key
- removing node data
- removing node link
The default module preserves anchor data. Use linked_list/advanced when
removal should allow caller-validated anchor data changes.
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.
The root is identified by its output reference. The helper authenticates the root, the linked node, and the continued root; preserves the root address/key; burns the folding node NFT; and enforces the root/node namespace checks.
This traverses inputs and expects exactly two authentic list inputs at the
list payment credential: the root input and the folding node input.
UTxOs without an asset under the list policy are independent of that state
and never need to be selected, spent, or cleaned up for the fold.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order.
additional_validations receives:
- root input
- root Lovelace change
- root data
- folding node input
- folding node Lovelace
- folding node key
- folding node data
- folding node link
- continued root data
Spending Script Helpers
Spend-side gate for structural operations.
This function is intentionally not a structural validator by itself. It
must only be used in a spend script whose paired list minting policy accepts
structural mint/burns exclusively through this module’s matching mint
helpers (init, insert_*, remove, fold_from_root, deinit, etc.).
The security invariant is split across scripts:
- the spend script uses this gate to permit structural list spends only when
a list-policy mint/burn is present;
- the minting policy proves the exact linked-list transition with the
corresponding mint helper.
Do not use this as standalone authorization for moving a list UTxO.
A UTxO at the same payment credential without an asset under the list policy
is not a list UTxO. This gate deliberately provides no list-state guarantees
for it. Such a UTxO is independent, cannot be forced into another party’s
transaction, and never needs to be consumed or cleaned up by the list.
Use this for spend branches that allow adding/removing list elements. Use
spend_for_updating_elements_data for
non-structural continuations where the element is reproduced and no
list-policy mint/burn occurs.
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 an individual element without changing the linked-list structure.
No list-policy mint/burn is allowed; address, NFT, constructor, and link are
preserved before UpdateValidation decides the Lovelace and data change.
additional_validations receives:
- preserved element address
- Lovelace change
- element key (
None for root, Some(key) for node)
- old data
- new data
- preserved link
inputs and outputs must be the complete, unmodified
ScriptContext.transaction.inputs and ScriptContext.transaction.outputs
lists in ledger order. The indexes are interpreted against those lists.
Exposed Helpers
Authenticate a root or node UTxO and pass its info to info_validations.
The UTxO must be an inline-datum singleton list element under the supplied
policy. The returned key is None for the root and Some(node_key) for a
node, where node_key excludes NodeKeyPrefix. Root keys and node-prefix
membership are checked before the callback runs.
Authenticate a root UTxO and pass its info to info_validations.
The UTxO must carry the configured root NFT and a Root datum. The callback
receives address, Lovelace, raw root payload data, and the root link. Use
this when a script only accepts roots and should reject node UTxOs
immediately.
Authenticate a node UTxO and pass its info to info_validations.
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, the node key with the prefix stripped, raw node payload data, and
the node link.
Low-level Helpers
validate_singular_authentic_input(
inputs: List<Input>,
nft_policy_id: PolicyId,
return: fn(Input, Lovelace, AssetName, GenericElementData, Link) -> Bool,
) -> Bool
Validate exactly one authentic list input while scanning all transaction
inputs.
Inputs with no asset under nft_policy_id are not list candidates and are
assigned no structural meaning, irrespective of payment credential. After
one candidate is found, this optimized scanner rejects a second singleton
list-shaped input under the same policy; it assigns no meaning to inputs
without an asset under that policy.
The narrow candidate check relies on authentic elements containing exactly
ADA plus one list NFT and on ledger values listing ADA first. Other value
shapes are not canonical base-list elements and are ignored.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-one claim
is only as complete as the supplied list.
Args: callback receives input, Lovelace, asset name, data, link.
Another internal helper, you probably don’t need this.
validate_dual_authentic_inputs(
anchor_input_outref: OutputReference,
inputs: List<Input>,
nft_policy_id: PolicyId,
with: fn(
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
) ->
Bool,
) -> Bool
Validate exactly two authentic list inputs while scanning all transaction
inputs.
This compact fold is specialized for the exact two-list-input shape at one
payment credential; it is not a general-purpose transaction-input
classifier. This specialization has no bearing on list membership, which is
authenticated only by assets under nft_policy_id.
anchor_input_outref selects which found input is passed to the callback as
the anchor. Validation fails if neither authentic input has that reference.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-two claim
is only as complete as the supplied list.
Args: callback receives anchor input, anchor Lovelace, anchor asset name,
anchor data, anchor link, removing input, removing Lovelace, removing asset
name, removing data, removing link.
Another internal helper, you probably don’t need this.
Spend-side gate for structural operations.
This function is intentionally not a structural validator by itself. It
must only be used in a spend script whose paired list minting policy accepts
structural mint/burns exclusively through this module’s matching mint
helpers (init, insert_*, remove, fold_from_root, deinit, etc.).
The security invariant is split across scripts:
- the spend script uses this gate to permit structural list spends only when a list-policy mint/burn is present;
- the minting policy proves the exact linked-list transition with the corresponding mint helper.
Do not use this as standalone authorization for moving a list UTxO.
A UTxO at the same payment credential without an asset under the list policy is not a list UTxO. This gate deliberately provides no list-state guarantees for it. Such a UTxO is independent, cannot be forced into another party’s transaction, and never needs to be consumed or cleaned up by the list.
Use this for spend branches that allow adding/removing list elements. Use
spend_for_updating_elements_data for
non-structural continuations where the element is reproduced and no
list-policy mint/burn occurs.
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 an individual element without changing the linked-list structure.
No list-policy mint/burn is allowed; address, NFT, constructor, and link are
preserved before UpdateValidation decides the Lovelace and data change.
additional_validations receives:
- preserved element address
- Lovelace change
- element key (
Nonefor root,Some(key)for node) - old data
- new data
- preserved link
inputs and outputs must be the complete, unmodified
ScriptContext.transaction.inputs and ScriptContext.transaction.outputs
lists in ledger order. The indexes are interpreted against those lists.
Authenticate a root or node UTxO and pass its info to info_validations.
The UTxO must be an inline-datum singleton list element under the supplied
policy. The returned key is None for the root and Some(node_key) for a
node, where node_key excludes NodeKeyPrefix. Root keys and node-prefix
membership are checked before the callback runs.
Authenticate a root UTxO and pass its info to info_validations.
The UTxO must carry the configured root NFT and a Root datum. The callback
receives address, Lovelace, raw root payload data, and the root link. Use
this when a script only accepts roots and should reject node UTxOs
immediately.
Authenticate a node UTxO and pass its info to info_validations.
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, the node key with the prefix stripped, raw node payload data, and
the node link.
Low-level Helpers
validate_singular_authentic_input(
inputs: List<Input>,
nft_policy_id: PolicyId,
return: fn(Input, Lovelace, AssetName, GenericElementData, Link) -> Bool,
) -> Bool
Validate exactly one authentic list input while scanning all transaction
inputs.
Inputs with no asset under nft_policy_id are not list candidates and are
assigned no structural meaning, irrespective of payment credential. After
one candidate is found, this optimized scanner rejects a second singleton
list-shaped input under the same policy; it assigns no meaning to inputs
without an asset under that policy.
The narrow candidate check relies on authentic elements containing exactly
ADA plus one list NFT and on ledger values listing ADA first. Other value
shapes are not canonical base-list elements and are ignored.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-one claim
is only as complete as the supplied list.
Args: callback receives input, Lovelace, asset name, data, link.
Another internal helper, you probably don’t need this.
validate_dual_authentic_inputs(
anchor_input_outref: OutputReference,
inputs: List<Input>,
nft_policy_id: PolicyId,
with: fn(
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
) ->
Bool,
) -> Bool
Validate exactly two authentic list inputs while scanning all transaction
inputs.
This compact fold is specialized for the exact two-list-input shape at one
payment credential; it is not a general-purpose transaction-input
classifier. This specialization has no bearing on list membership, which is
authenticated only by assets under nft_policy_id.
anchor_input_outref selects which found input is passed to the callback as
the anchor. Validation fails if neither authentic input has that reference.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-two claim
is only as complete as the supplied list.
Args: callback receives anchor input, anchor Lovelace, anchor asset name,
anchor data, anchor link, removing input, removing Lovelace, removing asset
name, removing data, removing link.
Another internal helper, you probably don’t need this.
validate_singular_authentic_input(
inputs: List<Input>,
nft_policy_id: PolicyId,
return: fn(Input, Lovelace, AssetName, GenericElementData, Link) -> Bool,
) -> Bool
Validate exactly one authentic list input while scanning all transaction inputs.
Inputs with no asset under nft_policy_id are not list candidates and are
assigned no structural meaning, irrespective of payment credential. After
one candidate is found, this optimized scanner rejects a second singleton
list-shaped input under the same policy; it assigns no meaning to inputs
without an asset under that policy.
The narrow candidate check relies on authentic elements containing exactly ADA plus one list NFT and on ledger values listing ADA first. Other value shapes are not canonical base-list elements and are ignored.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-one claim
is only as complete as the supplied list.
Args: callback receives input, Lovelace, asset name, data, link.
Another internal helper, you probably don’t need this.
validate_dual_authentic_inputs(
anchor_input_outref: OutputReference,
inputs: List<Input>,
nft_policy_id: PolicyId,
with: fn(
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
Input,
Lovelace,
AssetName,
GenericElementData,
Link,
) ->
Bool,
) -> Bool
Validate exactly two authentic list inputs while scanning all transaction inputs.
This compact fold is specialized for the exact two-list-input shape at one
payment credential; it is not a general-purpose transaction-input
classifier. This specialization has no bearing on list membership, which is
authenticated only by assets under nft_policy_id.
anchor_input_outref selects which found input is passed to the callback as
the anchor. Validation fails if neither authentic input has that reference.
inputs must be the complete, unmodified
ScriptContext.transaction.inputs list in ledger order. The exact-two claim
is only as complete as the supplied list.
Args: callback receives anchor input, anchor Lovelace, anchor asset name, anchor data, anchor link, removing input, removing Lovelace, removing asset name, removing data, removing link.
Another internal helper, you probably don’t need this.