A collection of tried and tested modules and functions for implementing common design patterns in Aiken
See the codeTo help facilitate faster development of Cardano smart contracts, we present a collection of tried and tested modules and functions for implementing common design patterns.
Based on our design-patterns repository.
Install the package with aiken:
aiken add anastasia-labs/aiken-design-patterns --version v1.8.0
And you'll be able to import functions of various patterns:
use aiken_design_patterns/merkelized_validator
use aiken_design_patterns/multi_utxo_indexer
use aiken_design_patterns/linked_list
use aiken_design_patterns/linked_list/advanced
use aiken_design_patterns/linked_list/nested
use aiken_design_patterns/parameter_validation
use aiken_design_patterns/parameter_validation/advanced
use aiken_design_patterns/singular_utxo_indexer
use aiken_design_patterns/stake_validator
use aiken_design_patterns/tx_level_minter
Check out validators/examples to see how the exposed functions can be used.
Here are the steps to compile and run the included tests:
git clone https://github.com/Anastasia-Labs/aiken-design-patterns
cd aiken-design-patterns
aiken build
aiken check
This pattern allows for delegating some computations to a given staking script.
The primary application for this is the so-called "withdraw zero trick," which is most effective for validations against multiple script inputs in the transaction.
With a minimal spending logic (which is executed for each UTxO), and an arbitrary withdrawal logic (which is executed only once), a much more optimized script can be implemented.
The module offers three functions, primarily meant to be implemented under spending endpoints:
validate_withdrawvalidate_withdraw_with_amountvalidate_withdraw_minimalUse validate_withdraw_minimal if you don't need to perform any validations on
either the staking script's redeemer or withdrawal Lovelace quantity.
All three functions go over the withdrawals list in the transaction. However,
validate_withdraw and validate_withdraw_with_amount also traverse the
redeemers field in order to let you validate against the redeemer (and the
withdrawal quantity in case of the latter).
The primary purpose of this pattern is to offer a more optimized and composable solution for a unique mapping between one input UTxO to one or many output UTxOs.
There are a total of 4 variations available:
[!NOTE] Neither of the singular UTxO indexer patterns provides protection against the double satisfaction vulnerability, as this can be done in multiple ways depending on the contract. However, they require a dedicated argument as a reminder for the potential requirement of implementing a protection against this vulnerability.
Depending on the variation, the functions you can provide are:
Very similar to the stake validator, this design pattern couples the spend and minting endpoints of a validator.
In other words, spend logic only ensures the minting endpoint executes. It does so by looking at the mint field and making sure a non-zero amount of its asset (i.e. with a policy identical to the provided script hash) are getting minted/burnt.
The arbitrary logic is passed to the minting policy so that it can be executed a single time for a given transaction.
The datatype that models validity range in Cardano currently allows for values that are either meaningless, or can have more than one representation. For example, since the values are integers, the inclusive flag for each end is redundant for most cases and can be omitted in favor of a predefined convention (e.g. a value should always be considered inclusive).
In this module we present a custom datatype that essentially reduces the value domain of the original validity range to a smaller one that eliminates meaningless instances and redundancies.
The datatype is defined as follows:
pub type NormalizedTimeRange {
ClosedRange { lower: Int, upper: Int }
FromNegInf { upper: Int }
ToPosInf { lower: Int }
Always
InvalidRange
}
The exposed function of the module (normalize_time_range), takes a
ValidityRange and returns this custom datatype.
Since transaction size is limited in Cardano, some validators benefit from a solution which allows them to delegate parts of their logic. This becomes more prominent in cases where such logic can greatly benefit from optimization solutions that trade computation resources for script sizes (e.g. table lookups can take up more space so that costly computations can be averted).
This design pattern offers an interface for off-loading such validations into an external observer/withdrawal script, so that the sizes of the scripts themselves can stay within the limits of Cardano.
[!NOTE] Be aware that total size of reference scripts is currently limited to 200KiB (204800 bytes), and they also impose additional fees in an exponential manner. See here and here for more info.
The exposed delegated_compute function from merkelized_validator expects 6
arguments:
Pairs of all redeemers within the current script context.redeemers listData to the format of the input expected
by the staking script's computationData to the expected output of
the computationThis function expects to find the given stake validator in the redeemers list,
such that its redeemer is of type ComputationRedeemer (which carries the
generic input argument(s) and the expected output(s)), makes sure provided
input(s) match the ones given to the validator through its redeemer, and returns
the output(s) (which are carried inside the withdrawal redeemer) so that you can
safely use them.
For defining a withdrawal logic that carries out the computation, use the
exposed computation_withdrawal_wrapper function. It expects 2 arguments:
ComputationRedeemer<a, b>. Note that a is the type of
input argument(s), and b is the type of output argument(s)a, and return
a value of type bIt validates that the given input(s) and output(s) match correctly with the provided computation logic.
There are also ValidationRedeemer<a>, validation_withdrawal_wrapper and
delegated_validation variants which can be used for validations that don't
return any outputs.
In some cases, validators need to be aware of instances of a parameterized script in order to have a more robust control over the flow of assets.
As a simple example, consider a minting script that needs to ensure the destination of its tokens can only be instances of a specific spending script, e.g. parameterized by users' wallets.
Since each different wallet leads to a different script address, without verifying instances, instances can only be seen as arbitrary scripts from the minting script's point of view.
This can be resolved by validating that an instance is the result of applying specific parameters to a given parameterized script.
The base aiken_design_patterns/parameter_validation
module hashes parameters into fixed-length fragments. To validate them on-chain,
some restrictions are needed:
The module provides two sets of functions: one for applying parameter(s) in the dependent script (i.e. the minting script in the example above), and one for wrapping parameterized scripts.
After defining your parameterized scripts, you'll need to generate instances of
them with dummy data in order to obtain the required prefix value for your
target script to utilize. Note that your prefix should be from a single CBOR
encoded result.
See the base parameter-validation example for these helpers in use.
The advanced module
supports a single arbitrary Data parameter. It lets a dependent validator
prove that an output uses the exact instance produced by applying a known
parameter value to a known parameterized script.
advanced.apply_param takes the script version, a precomputed Flat prefix, and
the parameter. It serialises the parameter canonically, handles variable-length
CBOR and Flat chunking, reconstructs the applied script, and returns its
ScriptHash for comparison with the output credential.
See the advanced example
and its prefix generator.
Run the generator with pnpm --dir tools/advanced-parameter-prefix generate.
Storing lists directly in datums is generally impractical: as the datum grows, the UTxO can become too expensive or impossible to spend.
A linked list stores the collection across many authenticated UTxOs. Each list element carries ADA, exactly one list NFT, an inline datum, and a link to its immediate successor.
Plutonomicon has a nice write-up of how this can be implemented with eUTxOs.
To provide an API as user-friendly as possible, the implementation handles
structural linked-list validations and provides the data needed for custom
application validations. This is why the API exposes primary list operations
such as init, insert_ascending, and remove, rather than asking each
contract to reassemble the structural checks from granular helpers.
The linked-list API is split across three modules:
aiken_design_patterns/linked_list
provides the default root/node list API. Its mint helpers are strict: they do
not allow unrelated mint/burn changes under the list NFT policy.aiken_design_patterns/linked_list/advanced
reuses the default Element type and extends the default API for reference
scripts and callbacks that see spent and continued anchor data. Its structural
node operations and deinit may expose permitted same-policy mint/burn changes
and same-policy inputs from other payment credentials; init and non-structural
updates remain strict. Additional mint/burn names must stay outside both the
reserved root key and the node-key namespace. Input asset names at other
credentials are exposed unchanged for application validation. In a correctly
wired policy, the reserved root token can never be among them: it is the one
policy-wide singleton minted into the canonical root at initialization.aiken_design_patterns/linked_list/nested
uses its own Element type and supports two-level linked lists with Root,
InnerRoot, and Node elements. Nested currently provides init, deinit,
insertion helpers, the structural spend gate for add/remove branches, and
non-structural update spends. Its insertion and deinit callbacks also receive
permitted non-reserved same-policy mint/burn changes and namespace-classified
inputs; custom read/remove logic must preserve the same structural invariants.See the generated docs pages above for module-specific details. They are long and elaborate many of the soft requirements in order to better guide agents.
List membership is authenticated by an asset under the list NFT policy, never by the payment credential alone. Anyone can create an output at the list payment credential without running either the spend script or the minting policy. An ADA-only UTxO, or a UTxO carrying only foreign-policy assets, is therefore not a list element even when it sits at that credential. It requires no linked-list structural validation, and no linked-list invariant may rely on its datum, value, continuation, or eventual spend. UTxOs at one credential remain independent: a transaction consumes only the inputs its builder explicitly selects, and an outside party cannot force another UTxO into a list transition. These UTxOs never need to be discovered, collected, spent, or cleaned up by the list protocol. Off-chain list discovery must authenticate the expected structural token and canonical element shape rather than treating every UTxO at the payment credential as state. The namespace-aware advanced and nested input scanners ignore inputs without list-policy assets completely. Only inputs carrying assets under the list policy enter their structural or non-structural classification.
Import the base linked_list module alongside any variant module you call.
Keep variant-specific operations in their variant modules; in particular,
nested datums must not be passed to the base update/read helpers.
use aiken_design_patterns/linked_list
use aiken_design_patterns/linked_list/advanced
// or: use aiken_design_patterns/linked_list/nested
Contracts using these modules must keep all authenticated list elements controlled by one spend script/payment credential and one list NFT minting policy:
Element
type. For base and advanced lists this is Element<RootType, NodeType>;
for nested lists this is Element<RootType, InnerRootType, NodeType>.init goes to that spend script credential.spend_for_adding_or_removing_an_element.spend_for_updating_elements_data.ScriptContext.transaction.inputs list in
ledger order to every linked-list helper inputs argument. Do not pass a
filtered, reordered, reconstructed, or redeemer-provided list. Exact
structural-input counts and namespace-aware input collection are guaranteed
only across the supplied list.Output argument passed to a linked-list mint helper must be selected
from the script context transaction outputs. Helpers authenticate the
selected outputs as list UTxOs, but intentionally leave the selection method
to the caller. A contract may pick by redeemer-provided output index, filter
ScriptContext.transaction.outputs, use list.find, or use another
deterministic method. What matters is that the final Output value comes
from the transaction outputs, not from redeemer data or a locally constructed
value.<list_nft_policy_id, root_key> globally across every
redeemer branch of the list policy. A one-time init is the only branch that
may mint it, and it mints exactly one into the canonical root at the list
payment credential. Structural and update continuations keep it there;
deinit is the only branch that may burn it. Every application-specific
same-policy branch must also reject changes to root_key. Under this
policy-wide invariant, a root_key token at another payment credential is
unreachable and must not be treated as valid external state.node_key_prefix ++ node_key. The library assumes this convention
instead of adding repeated on-chain checks to every operation; agents wiring
a contract must treat this as a deployment precondition, not as something
recovered by the helpers later.These rules preserve the invariant that linked-list NFTs cannot leave the list
spend script/payment credential; they make no claim about UTxOs at that
credential which carry no list-policy asset. Continued anchors are checked by
full address equality; newly minted nodes share the anchor payment credential,
which lets callers choose staking parts for new nodes. If a callback does not
receive a produced element address directly, the corresponding Output is an
argument the caller supplied to the helper and can be captured by the callback.
The structural spend gate only requires a list-policy mint/burn to occur; it is
not standalone authorization. The paired minting policy must only accept
structural mint/burns through a matching linked-list mint helper where one is
provided, or through custom validation that proves the same invariants. Example
validators in this repository demonstrate API wiring, but they do not replace
contract-specific authorization or state-transition invariants.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Aiken
97.6%
JavaScript
2.1%
A collection of tried and tested modules and functions for implementing common design patterns in Aiken
See the codeTo help facilitate faster development of Cardano smart contracts, we present a collection of tried and tested modules and functions for implementing common design patterns.
Based on our design-patterns repository.
Install the package with aiken:
aiken add anastasia-labs/aiken-design-patterns --version v1.8.0
And you'll be able to import functions of various patterns:
use aiken_design_patterns/merkelized_validator
use aiken_design_patterns/multi_utxo_indexer
use aiken_design_patterns/linked_list
use aiken_design_patterns/linked_list/advanced
use aiken_design_patterns/linked_list/nested
use aiken_design_patterns/parameter_validation
use aiken_design_patterns/parameter_validation/advanced
use aiken_design_patterns/singular_utxo_indexer
use aiken_design_patterns/stake_validator
use aiken_design_patterns/tx_level_minter
Check out validators/examples to see how the exposed functions can be used.
Here are the steps to compile and run the included tests:
git clone https://github.com/Anastasia-Labs/aiken-design-patterns
cd aiken-design-patterns
aiken build
aiken check
This pattern allows for delegating some computations to a given staking script.
The primary application for this is the so-called "withdraw zero trick," which is most effective for validations against multiple script inputs in the transaction.
With a minimal spending logic (which is executed for each UTxO), and an arbitrary withdrawal logic (which is executed only once), a much more optimized script can be implemented.
The module offers three functions, primarily meant to be implemented under spending endpoints:
validate_withdrawvalidate_withdraw_with_amountvalidate_withdraw_minimalUse validate_withdraw_minimal if you don't need to perform any validations on
either the staking script's redeemer or withdrawal Lovelace quantity.
All three functions go over the withdrawals list in the transaction. However,
validate_withdraw and validate_withdraw_with_amount also traverse the
redeemers field in order to let you validate against the redeemer (and the
withdrawal quantity in case of the latter).
The primary purpose of this pattern is to offer a more optimized and composable solution for a unique mapping between one input UTxO to one or many output UTxOs.
There are a total of 4 variations available:
[!NOTE] Neither of the singular UTxO indexer patterns provides protection against the double satisfaction vulnerability, as this can be done in multiple ways depending on the contract. However, they require a dedicated argument as a reminder for the potential requirement of implementing a protection against this vulnerability.
Depending on the variation, the functions you can provide are:
Very similar to the stake validator, this design pattern couples the spend and minting endpoints of a validator.
In other words, spend logic only ensures the minting endpoint executes. It does so by looking at the mint field and making sure a non-zero amount of its asset (i.e. with a policy identical to the provided script hash) are getting minted/burnt.
The arbitrary logic is passed to the minting policy so that it can be executed a single time for a given transaction.
The datatype that models validity range in Cardano currently allows for values that are either meaningless, or can have more than one representation. For example, since the values are integers, the inclusive flag for each end is redundant for most cases and can be omitted in favor of a predefined convention (e.g. a value should always be considered inclusive).
In this module we present a custom datatype that essentially reduces the value domain of the original validity range to a smaller one that eliminates meaningless instances and redundancies.
The datatype is defined as follows:
pub type NormalizedTimeRange {
ClosedRange { lower: Int, upper: Int }
FromNegInf { upper: Int }
ToPosInf { lower: Int }
Always
InvalidRange
}
The exposed function of the module (normalize_time_range), takes a
ValidityRange and returns this custom datatype.
Since transaction size is limited in Cardano, some validators benefit from a solution which allows them to delegate parts of their logic. This becomes more prominent in cases where such logic can greatly benefit from optimization solutions that trade computation resources for script sizes (e.g. table lookups can take up more space so that costly computations can be averted).
This design pattern offers an interface for off-loading such validations into an external observer/withdrawal script, so that the sizes of the scripts themselves can stay within the limits of Cardano.
[!NOTE] Be aware that total size of reference scripts is currently limited to 200KiB (204800 bytes), and they also impose additional fees in an exponential manner. See here and here for more info.
The exposed delegated_compute function from merkelized_validator expects 6
arguments:
Pairs of all redeemers within the current script context.redeemers listData to the format of the input expected
by the staking script's computationData to the expected output of
the computationThis function expects to find the given stake validator in the redeemers list,
such that its redeemer is of type ComputationRedeemer (which carries the
generic input argument(s) and the expected output(s)), makes sure provided
input(s) match the ones given to the validator through its redeemer, and returns
the output(s) (which are carried inside the withdrawal redeemer) so that you can
safely use them.
For defining a withdrawal logic that carries out the computation, use the
exposed computation_withdrawal_wrapper function. It expects 2 arguments:
ComputationRedeemer<a, b>. Note that a is the type of
input argument(s), and b is the type of output argument(s)a, and return
a value of type bIt validates that the given input(s) and output(s) match correctly with the provided computation logic.
There are also ValidationRedeemer<a>, validation_withdrawal_wrapper and
delegated_validation variants which can be used for validations that don't
return any outputs.
In some cases, validators need to be aware of instances of a parameterized script in order to have a more robust control over the flow of assets.
As a simple example, consider a minting script that needs to ensure the destination of its tokens can only be instances of a specific spending script, e.g. parameterized by users' wallets.
Since each different wallet leads to a different script address, without verifying instances, instances can only be seen as arbitrary scripts from the minting script's point of view.
This can be resolved by validating that an instance is the result of applying specific parameters to a given parameterized script.
The base aiken_design_patterns/parameter_validation
module hashes parameters into fixed-length fragments. To validate them on-chain,
some restrictions are needed:
The module provides two sets of functions: one for applying parameter(s) in the dependent script (i.e. the minting script in the example above), and one for wrapping parameterized scripts.
After defining your parameterized scripts, you'll need to generate instances of
them with dummy data in order to obtain the required prefix value for your
target script to utilize. Note that your prefix should be from a single CBOR
encoded result.
See the base parameter-validation example for these helpers in use.
The advanced module
supports a single arbitrary Data parameter. It lets a dependent validator
prove that an output uses the exact instance produced by applying a known
parameter value to a known parameterized script.
advanced.apply_param takes the script version, a precomputed Flat prefix, and
the parameter. It serialises the parameter canonically, handles variable-length
CBOR and Flat chunking, reconstructs the applied script, and returns its
ScriptHash for comparison with the output credential.
See the advanced example
and its prefix generator.
Run the generator with pnpm --dir tools/advanced-parameter-prefix generate.
Storing lists directly in datums is generally impractical: as the datum grows, the UTxO can become too expensive or impossible to spend.
A linked list stores the collection across many authenticated UTxOs. Each list element carries ADA, exactly one list NFT, an inline datum, and a link to its immediate successor.
Plutonomicon has a nice write-up of how this can be implemented with eUTxOs.
To provide an API as user-friendly as possible, the implementation handles
structural linked-list validations and provides the data needed for custom
application validations. This is why the API exposes primary list operations
such as init, insert_ascending, and remove, rather than asking each
contract to reassemble the structural checks from granular helpers.
The linked-list API is split across three modules:
aiken_design_patterns/linked_list
provides the default root/node list API. Its mint helpers are strict: they do
not allow unrelated mint/burn changes under the list NFT policy.aiken_design_patterns/linked_list/advanced
reuses the default Element type and extends the default API for reference
scripts and callbacks that see spent and continued anchor data. Its structural
node operations and deinit may expose permitted same-policy mint/burn changes
and same-policy inputs from other payment credentials; init and non-structural
updates remain strict. Additional mint/burn names must stay outside both the
reserved root key and the node-key namespace. Input asset names at other
credentials are exposed unchanged for application validation. In a correctly
wired policy, the reserved root token can never be among them: it is the one
policy-wide singleton minted into the canonical root at initialization.aiken_design_patterns/linked_list/nested
uses its own Element type and supports two-level linked lists with Root,
InnerRoot, and Node elements. Nested currently provides init, deinit,
insertion helpers, the structural spend gate for add/remove branches, and
non-structural update spends. Its insertion and deinit callbacks also receive
permitted non-reserved same-policy mint/burn changes and namespace-classified
inputs; custom read/remove logic must preserve the same structural invariants.See the generated docs pages above for module-specific details. They are long and elaborate many of the soft requirements in order to better guide agents.
List membership is authenticated by an asset under the list NFT policy, never by the payment credential alone. Anyone can create an output at the list payment credential without running either the spend script or the minting policy. An ADA-only UTxO, or a UTxO carrying only foreign-policy assets, is therefore not a list element even when it sits at that credential. It requires no linked-list structural validation, and no linked-list invariant may rely on its datum, value, continuation, or eventual spend. UTxOs at one credential remain independent: a transaction consumes only the inputs its builder explicitly selects, and an outside party cannot force another UTxO into a list transition. These UTxOs never need to be discovered, collected, spent, or cleaned up by the list protocol. Off-chain list discovery must authenticate the expected structural token and canonical element shape rather than treating every UTxO at the payment credential as state. The namespace-aware advanced and nested input scanners ignore inputs without list-policy assets completely. Only inputs carrying assets under the list policy enter their structural or non-structural classification.
Import the base linked_list module alongside any variant module you call.
Keep variant-specific operations in their variant modules; in particular,
nested datums must not be passed to the base update/read helpers.
use aiken_design_patterns/linked_list
use aiken_design_patterns/linked_list/advanced
// or: use aiken_design_patterns/linked_list/nested
Contracts using these modules must keep all authenticated list elements controlled by one spend script/payment credential and one list NFT minting policy:
Element
type. For base and advanced lists this is Element<RootType, NodeType>;
for nested lists this is Element<RootType, InnerRootType, NodeType>.init goes to that spend script credential.spend_for_adding_or_removing_an_element.spend_for_updating_elements_data.ScriptContext.transaction.inputs list in
ledger order to every linked-list helper inputs argument. Do not pass a
filtered, reordered, reconstructed, or redeemer-provided list. Exact
structural-input counts and namespace-aware input collection are guaranteed
only across the supplied list.Output argument passed to a linked-list mint helper must be selected
from the script context transaction outputs. Helpers authenticate the
selected outputs as list UTxOs, but intentionally leave the selection method
to the caller. A contract may pick by redeemer-provided output index, filter
ScriptContext.transaction.outputs, use list.find, or use another
deterministic method. What matters is that the final Output value comes
from the transaction outputs, not from redeemer data or a locally constructed
value.<list_nft_policy_id, root_key> globally across every
redeemer branch of the list policy. A one-time init is the only branch that
may mint it, and it mints exactly one into the canonical root at the list
payment credential. Structural and update continuations keep it there;
deinit is the only branch that may burn it. Every application-specific
same-policy branch must also reject changes to root_key. Under this
policy-wide invariant, a root_key token at another payment credential is
unreachable and must not be treated as valid external state.node_key_prefix ++ node_key. The library assumes this convention
instead of adding repeated on-chain checks to every operation; agents wiring
a contract must treat this as a deployment precondition, not as something
recovered by the helpers later.These rules preserve the invariant that linked-list NFTs cannot leave the list
spend script/payment credential; they make no claim about UTxOs at that
credential which carry no list-policy asset. Continued anchors are checked by
full address equality; newly minted nodes share the anchor payment credential,
which lets callers choose staking parts for new nodes. If a callback does not
receive a produced element address directly, the corresponding Output is an
argument the caller supplied to the helper and can be captured by the callback.
The structural spend gate only requires a list-policy mint/burn to occur; it is
not standalone authorization. The paired minting policy must only accept
structural mint/burns through a matching linked-list mint helper where one is
provided, or through custom validation that proves the same invariants. Example
validators in this repository demonstrate API wiring, but they do not replace
contract-specific authorization or state-transition invariants.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Aiken
97.6%
JavaScript
2.1%