aiken_design_patterns/linked_list
Storing lists in datums is generally impractical, as its growth can lead to unspendable UTxOs due to limited resources available on-chain.
A linked list is a construct for storing an infinitely large array of elements on-chain, such that each element is represented with a UTxO that points to its immediate successor.
The presumption throughout is that the datum of the spending contract which
holds the linked list UTxOs is of type Element<a, b>. So the recommended
approach is to define your datum as its applied alias:
pub type Datum = linked_list.Element<RootType, NodeType>
There are two opinionated designs that should be highlighted:
- The elements are required to have no scripts attached to them. The continuations are already provided with too many values. Providing their reference script fields would overwhelm the API even further. So far this doesn’t seem to be a needed feature.
- While continued anchor nodes are validated to go to the same address, for new/removed nodes only the payment parts are validated to match the anchors’. This allows customization of staking parts of new nodes, while preserving the staking parts of existing elements.
Types
The terminology used here is as follows:
Rootis the first element of the linked listNodeis any other element in a linked list other than the rootElementis the umbrella term to refer to either the root or nodes
You probably won’t need to interact with this.
Constructors
-
Element { data: ElementData<root_data, node_data>, link: Link }
Similar to Element above, the API is designed so that you
wouldn’t need to work with this datatype directly.
Continuations that provide element data implicitly signal whether it should
be coerced into a root_data or a node_data through the (potential) key.
That is, if no key is provided, it means the element at hand is a root
element.
Constructors
-
Root { data: root_data } -
Node { data: node_data }
Similar to Eval, but with polymorphic return type. This allows
returning custom types with get_element_info.
Alias
ElementEval<a> = fn(PolicyId, RootKey, NodeKeyPrefix, NodeKeyPrefixLength) -> a
Type alias for the continuation function passed to
get_element_info.
Alias
ElementInfo<a> = fn(Lovelace, Option<NodeKey>, Data, Link) -> a
This is the datatype for allowing you to provide environment constants. To
be a bit nerdy, this is essentially a Reader monad.
To prevent excessive overhead, this function requires the length of the key prefix to be provided explicitly, next to the key prefix itself (otherwise the length should have been validated on-chain).
The length of the
RootKeycan be 32 bytes at the maximum. On the other hand, the length of any of theNodeKeys can be at most(32 - NodeKeyPrefixLength). “Length” here refers to the number of bytes after the asset name is decoded from any of the encoding schemes (e.g. UTF-8).
Alias
Eval = fn(PolicyId, RootKey, NodeKeyPrefix, NodeKeyPrefixLength) -> Bool
Alias
Link = Option<NodeKey>
Alias
LovelaceChange = Lovelace
Alias
NodeData = Data
Alias for representing the keys of linked list nodes. This must be at least one byte long, and contracts should implement validations on them depending on their needs.
Requiring keys to be unique is the recommended approach (e.g. making sure it’s hash of an input’s out ref). If you do need to allow duplicate keys, proceed with extreme caution as linked list integrity breaks without some other means of preserving it.
As with the general principle of this API, for ordered operations (e.g.
insert_ascending), proper ordering is already
validated.
Alias
NodeKey = ByteArray
Bytes prefixing all linked list nodes (i.e. all elements except for the root).
Note that the total number of bytes allowed for NFTs of linked list elements can be 32 bytes at most. Therefore, if for example your prefix occupies 4 bytes, your keys can have 28 bytes at most.
Alias
NodeKeyPrefix = ByteArray
A sort of boilerplate to reduce execution budgets. You can use const to
leverage Aiken compiler’s optimizations:
pub const node_key_prefix = "🔗"
pub const node_key_prefix_length =
bytearray.length(node_key_prefix)
Alias
NodeKeyPrefixLength = Int
Alias
RootData = Data
Similar to Eval, but for (init)[#init] and (deinit)[#deinit],
which don’t validate node key prefixes.
Alias
RootEval = fn(PolicyId, RootKey) -> Bool
An alias for the asset name of the NFT held in the root element. This can be zero bytes or more (32 bytes at most).
Keep in mind that some UTF-8 characters occupy more than 1 byte. For example the tree emoji (🌳) takes up 4 bytes.
Alias
RootKey = AssetName
Functions
Finalization Functions
run_eval_with(
reader: Eval,
list_nft_policy_id: PolicyId,
root_key: RootKey,
node_key_prefix: NodeKeyPrefix,
node_key_prefix_length: NodeKeyPrefixLength,
) -> Bool
This typically ends up as boilerplate by defining a helper like this:
use aiken_design_patterns/linked_list
const node_key_prefix = "NODE"
const node_key_prefix_length =
bytearray.length(node_key_prefix)
pub fn finalize_linked_list(
eval: linked_list.Eval,
list_nft_policy_id: PolicyId,
) -> Bool {
linked_list.run_eval_with(
eval,
list_nft_policy_id,
node_key_prefix,
node_key_prefix_length,
)
}
As an example, with this you’ll be able to turn your Eval results to
Bools as such:
// In your minting contract:
let linked_list_eval = insert_ascending(...)
expect linked_list_eval |> finalize_linked_list(own_policy_id)
Initialization and De-initialization
init(
nonce_validated: Bool,
produced_element_output: Output,
tx_mint: Value,
root_validator: fn(Lovelace, Data) -> Bool,
) -> RootEval
Initialize a linked list, i.e. produce a UTxO which contains some ADA and
the authentication NFT, with a Root element data. The asset name of the
NFT (i.e. RootKey) is something that must be validated by passing the
result of this function to run_root_with.
deinit(
root_input: Input,
tx_mint: Value,
root_validator: fn(Lovelace, Data) -> Bool,
) -> RootEval
Deinitialize an empty list.
Element Addition and Removal
insert_ascending(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
It is assumed the address from which the anchor element comes from is the
destination address for the new node.
The additional_validations argument provides you with 7 values:
- Change in Lovelace count from anchor input to continued anchor output
- If the anchor element is a block, its key (i.e. asset name without the
prefix) is provided
- Underlying data of the anchor element – note that this can come from
either a
Root or a Node
- Lovelace count of the new node UTxO
- Key (i.e. asset name without the prefix) of the new node
- Underlying data of the new node – note that this is extracted from a
Node
Link of the new node
insert_descending(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
This is exactly like insert_ascending, with the
exception that the inserted key is expected to be less than the anchor’s,
and greater than anchor’s link (if any).
append_unordered(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
) ->
Bool,
) -> Eval
Append a new element at the very end of an unordered list. There is no
validation for the new key to be greater than the anchor element’s.
additional_validations takes the same arguments as
insert_ordered.
prepend_unordered(
root_element_input: Input,
continued_root_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
RootData,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
Prepend a new element at the very start of an unordered list. There is no
validation for the new key to be less than the anchor/root element’s
potential link.
additional_validations takes the same arguments as
insert_ordered, without a possible element key, as the
anchor is required to be root, which is a value that is already accessible.
remove(
anchor_element_input: Input,
removing_node_input: Input,
continued_anchor_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
Remove a node. It expects two spent UTxOs: the node subject to removal, and
its previous element (i.e. anchor element).
additional_validations takes the same arguments as
insert_ordered, the difference being last NodeKey and
NodeData are from the removing node.
Fold Functions
fold_from_root(
anchor_root_input: Input,
folding_node_input: Input,
continued_anchor_root_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
RootData,
Lovelace,
NodeKey,
NodeData,
Link,
RootData,
) ->
Bool,
) -> Eval
Spend a root element and its link, reproduce the root while expecting the
node to be burnt. Additionally, performs an arbitrary validation on keys and
underlying data.
This function ensures:
- Authenticity of the UTxOs and root’s continued counterpart
- The anchor is root, and that it correctly points to the folding node
- The addresses are the same for all 3 UTxOs
- Folded node is burnt
- Root’s key is correct, and node’s key is prefixed as expected
The function you define for additional_validations is provided with 7
values (in order):
- Change in Lovelace count from input root to output
- Underlying data of the root
- Lovelace count of the folding node
- Key (i.e. asset name) of the folding node
- Underlying data of the folding node
Link of the folding node
- Underlying data of the reproduced/continued root
Spending Script Helpers
Simply checks if there are any tokens getting minted/burnt in transaction’s
mint field. How you provide list_nft_policy_id is most likely the
“coupling” needed between the spending and minting endpoints.
spend_for_updating_elements_data(
element_input_index: Int,
continued_element_output_index: Int,
element_input_outref: OutputReference,
inputs: List<Input>,
outputs: List<Output>,
tx_mint: Value,
additional_validations: fn(LovelaceChange, Option<NodeKey>, Data, Data) ->
Bool,
) -> Eval
For spending an individual element within a linked list and reproducing it
with an updated data, without affecting the structure of the linked list.
The continuation provides you with:
- Change in Lovelace count from input to output (i.e. output minus input).
- A possible node key (i.e. its asset name without the prefix). It’ll be
None if the spent element is root.
- Input underlying data.
- Updated data in the reproduced element.
The transaction’s mint is required to ensure no link list assets are minted
or burnt.
Exposed Helpers
This should generally not be needed, but in cases where another script wants
to validate expenditure from the linked list, this function can help.
After validating the authenticity of the provided element UTxO, it provides
the continuation with:
- A possible
NodeKey (i.e. it will be None if the element UTxO is root)
- Underlying data of the element
- Element’s link
Unlike other helpers, this one returns an ElementEval<a>. This is very
similar to Eval. However, the type variable a allows the final
evaluation to result in any typte rather than being restricted to Bool.
run_eval_with(
reader: Eval,
list_nft_policy_id: PolicyId,
root_key: RootKey,
node_key_prefix: NodeKeyPrefix,
node_key_prefix_length: NodeKeyPrefixLength,
) -> Bool
This typically ends up as boilerplate by defining a helper like this:
use aiken_design_patterns/linked_list
const node_key_prefix = "NODE"
const node_key_prefix_length =
bytearray.length(node_key_prefix)
pub fn finalize_linked_list(
eval: linked_list.Eval,
list_nft_policy_id: PolicyId,
) -> Bool {
linked_list.run_eval_with(
eval,
list_nft_policy_id,
node_key_prefix,
node_key_prefix_length,
)
}
As an example, with this you’ll be able to turn your Eval results to
Bools as such:
// In your minting contract:
let linked_list_eval = insert_ascending(...)
expect linked_list_eval |> finalize_linked_list(own_policy_id)
init(
nonce_validated: Bool,
produced_element_output: Output,
tx_mint: Value,
root_validator: fn(Lovelace, Data) -> Bool,
) -> RootEval
Initialize a linked list, i.e. produce a UTxO which contains some ADA and
the authentication NFT, with a Root element data. The asset name of the
NFT (i.e. RootKey) is something that must be validated by passing the
result of this function to run_root_with.
deinit(
root_input: Input,
tx_mint: Value,
root_validator: fn(Lovelace, Data) -> Bool,
) -> RootEval
Deinitialize an empty list.
Element Addition and Removal
insert_ascending(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
It is assumed the address from which the anchor element comes from is the
destination address for the new node.
The additional_validations argument provides you with 7 values:
- Change in Lovelace count from anchor input to continued anchor output
- If the anchor element is a block, its key (i.e. asset name without the
prefix) is provided
- Underlying data of the anchor element – note that this can come from
either a
Root or a Node
- Lovelace count of the new node UTxO
- Key (i.e. asset name without the prefix) of the new node
- Underlying data of the new node – note that this is extracted from a
Node
Link of the new node
insert_descending(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
This is exactly like insert_ascending, with the
exception that the inserted key is expected to be less than the anchor’s,
and greater than anchor’s link (if any).
append_unordered(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
) ->
Bool,
) -> Eval
Append a new element at the very end of an unordered list. There is no
validation for the new key to be greater than the anchor element’s.
additional_validations takes the same arguments as
insert_ordered.
prepend_unordered(
root_element_input: Input,
continued_root_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
RootData,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
Prepend a new element at the very start of an unordered list. There is no
validation for the new key to be less than the anchor/root element’s
potential link.
additional_validations takes the same arguments as
insert_ordered, without a possible element key, as the
anchor is required to be root, which is a value that is already accessible.
remove(
anchor_element_input: Input,
removing_node_input: Input,
continued_anchor_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
Remove a node. It expects two spent UTxOs: the node subject to removal, and
its previous element (i.e. anchor element).
additional_validations takes the same arguments as
insert_ordered, the difference being last NodeKey and
NodeData are from the removing node.
Fold Functions
fold_from_root(
anchor_root_input: Input,
folding_node_input: Input,
continued_anchor_root_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
RootData,
Lovelace,
NodeKey,
NodeData,
Link,
RootData,
) ->
Bool,
) -> Eval
Spend a root element and its link, reproduce the root while expecting the
node to be burnt. Additionally, performs an arbitrary validation on keys and
underlying data.
This function ensures:
- Authenticity of the UTxOs and root’s continued counterpart
- The anchor is root, and that it correctly points to the folding node
- The addresses are the same for all 3 UTxOs
- Folded node is burnt
- Root’s key is correct, and node’s key is prefixed as expected
The function you define for additional_validations is provided with 7
values (in order):
- Change in Lovelace count from input root to output
- Underlying data of the root
- Lovelace count of the folding node
- Key (i.e. asset name) of the folding node
- Underlying data of the folding node
Link of the folding node
- Underlying data of the reproduced/continued root
Spending Script Helpers
Simply checks if there are any tokens getting minted/burnt in transaction’s
mint field. How you provide list_nft_policy_id is most likely the
“coupling” needed between the spending and minting endpoints.
spend_for_updating_elements_data(
element_input_index: Int,
continued_element_output_index: Int,
element_input_outref: OutputReference,
inputs: List<Input>,
outputs: List<Output>,
tx_mint: Value,
additional_validations: fn(LovelaceChange, Option<NodeKey>, Data, Data) ->
Bool,
) -> Eval
For spending an individual element within a linked list and reproducing it
with an updated data, without affecting the structure of the linked list.
The continuation provides you with:
- Change in Lovelace count from input to output (i.e. output minus input).
- A possible node key (i.e. its asset name without the prefix). It’ll be
None if the spent element is root.
- Input underlying data.
- Updated data in the reproduced element.
The transaction’s mint is required to ensure no link list assets are minted
or burnt.
Exposed Helpers
This should generally not be needed, but in cases where another script wants
to validate expenditure from the linked list, this function can help.
After validating the authenticity of the provided element UTxO, it provides
the continuation with:
- A possible
NodeKey (i.e. it will be None if the element UTxO is root)
- Underlying data of the element
- Element’s link
Unlike other helpers, this one returns an ElementEval<a>. This is very
similar to Eval. However, the type variable a allows the final
evaluation to result in any typte rather than being restricted to Bool.
insert_ascending(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
It is assumed the address from which the anchor element comes from is the destination address for the new node.
The additional_validations argument provides you with 7 values:
- Change in Lovelace count from anchor input to continued anchor output
- If the anchor element is a block, its key (i.e. asset name without the prefix) is provided
- Underlying data of the anchor element – note that this can come from
either a
Rootor aNode - Lovelace count of the new node UTxO
- Key (i.e. asset name without the prefix) of the new node
- Underlying data of the new node – note that this is extracted from a
Node Linkof the new node
insert_descending(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
This is exactly like insert_ascending, with the
exception that the inserted key is expected to be less than the anchor’s,
and greater than anchor’s link (if any).
append_unordered(
anchor_element_input: Input,
continued_anchor_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
) ->
Bool,
) -> Eval
Append a new element at the very end of an unordered list. There is no validation for the new key to be greater than the anchor element’s.
additional_validations takes the same arguments as
insert_ordered.
prepend_unordered(
root_element_input: Input,
continued_root_element_output: Output,
new_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
RootData,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
Prepend a new element at the very start of an unordered list. There is no validation for the new key to be less than the anchor/root element’s potential link.
additional_validations takes the same arguments as
insert_ordered, without a possible element key, as the
anchor is required to be root, which is a value that is already accessible.
remove(
anchor_element_input: Input,
removing_node_input: Input,
continued_anchor_element_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
Option<NodeKey>,
Data,
Lovelace,
NodeKey,
NodeData,
Link,
) ->
Bool,
) -> Eval
Remove a node. It expects two spent UTxOs: the node subject to removal, and its previous element (i.e. anchor element).
additional_validations takes the same arguments as
insert_ordered, the difference being last NodeKey and
NodeData are from the removing node.
fold_from_root(
anchor_root_input: Input,
folding_node_input: Input,
continued_anchor_root_output: Output,
tx_mint: Value,
additional_validations: fn(
LovelaceChange,
RootData,
Lovelace,
NodeKey,
NodeData,
Link,
RootData,
) ->
Bool,
) -> Eval
Spend a root element and its link, reproduce the root while expecting the node to be burnt. Additionally, performs an arbitrary validation on keys and underlying data.
This function ensures:
- Authenticity of the UTxOs and root’s continued counterpart
- The anchor is root, and that it correctly points to the folding node
- The addresses are the same for all 3 UTxOs
- Folded node is burnt
- Root’s key is correct, and node’s key is prefixed as expected
The function you define for additional_validations is provided with 7
values (in order):
- Change in Lovelace count from input root to output
- Underlying data of the root
- Lovelace count of the folding node
- Key (i.e. asset name) of the folding node
- Underlying data of the folding node
Linkof the folding node- Underlying data of the reproduced/continued root
Spending Script Helpers
Simply checks if there are any tokens getting minted/burnt in transaction’s
mint field. How you provide list_nft_policy_id is most likely the
“coupling” needed between the spending and minting endpoints.
spend_for_updating_elements_data(
element_input_index: Int,
continued_element_output_index: Int,
element_input_outref: OutputReference,
inputs: List<Input>,
outputs: List<Output>,
tx_mint: Value,
additional_validations: fn(LovelaceChange, Option<NodeKey>, Data, Data) ->
Bool,
) -> Eval
For spending an individual element within a linked list and reproducing it
with an updated data, without affecting the structure of the linked list.
The continuation provides you with:
- Change in Lovelace count from input to output (i.e. output minus input).
- A possible node key (i.e. its asset name without the prefix). It’ll be
None if the spent element is root.
- Input underlying data.
- Updated data in the reproduced element.
The transaction’s mint is required to ensure no link list assets are minted
or burnt.
Exposed Helpers
This should generally not be needed, but in cases where another script wants
to validate expenditure from the linked list, this function can help.
After validating the authenticity of the provided element UTxO, it provides
the continuation with:
- A possible
NodeKey (i.e. it will be None if the element UTxO is root)
- Underlying data of the element
- Element’s link
Unlike other helpers, this one returns an ElementEval<a>. This is very
similar to Eval. However, the type variable a allows the final
evaluation to result in any typte rather than being restricted to Bool.
Simply checks if there are any tokens getting minted/burnt in transaction’s
mint field. How you provide list_nft_policy_id is most likely the
“coupling” needed between the spending and minting endpoints.
spend_for_updating_elements_data(
element_input_index: Int,
continued_element_output_index: Int,
element_input_outref: OutputReference,
inputs: List<Input>,
outputs: List<Output>,
tx_mint: Value,
additional_validations: fn(LovelaceChange, Option<NodeKey>, Data, Data) ->
Bool,
) -> Eval
For spending an individual element within a linked list and reproducing it with an updated data, without affecting the structure of the linked list.
The continuation provides you with:
- Change in Lovelace count from input to output (i.e. output minus input).
- A possible node key (i.e. its asset name without the prefix). It’ll be
Noneif the spent element is root. - Input underlying data.
- Updated data in the reproduced element.
The transaction’s mint is required to ensure no link list assets are minted or burnt.
This should generally not be needed, but in cases where another script wants to validate expenditure from the linked list, this function can help.
After validating the authenticity of the provided element UTxO, it provides the continuation with:
- A possible
NodeKey(i.e. it will beNoneif the element UTxO is root) - Underlying data of the element
- Element’s link
Unlike other helpers, this one returns an ElementEval<a>. This is very
similar to Eval. However, the type variable a allows the final
evaluation to result in any typte rather than being restricted to Bool.