Aiken is a functional programming language created for Cardano smart contract development. It prioritizes on-chain execution and offers a user-friendly approach for building secure and efficient smart contracts, making it a valuable choice for developers aiming to create robust on-chain applications.
In this tutorial, we will walk you through the process of writing a simple smart contract in Aiken, and create 2 transactions to lock and unlock assets on the Cardano blockchain.
You can also try the live demo and here are the codes on the GitHub repository
System setup
This section will guide you through the process of setting up your system compile Aiken smart contracts. You can skip this section if you have already set up your system or do not wish to compile the contract.
You can also check the installation instructions on the Aiken website for more information.
Using aikup (on Linux & MacOS only)
If you are using Linux or MacOS, you can use the utility tool to download and manage Aiken's pre-compiled executables.
You can install the Aiken CLI by running the following command in your terminal:
$ curl -sSfL https://install.aiken-lang.org | bash $ aikup
From sources (all platforms)
Aiken is written in Rust, so you will need to install Rust and Cargo to compile the smart contract. You can install Rust by following the instructions on the Rust website.
Next, you will need Cargo, the Rust package manager. You can install Cargo by following the instructions on the Cargo website.
You will know you have successfully installed Rust and Cargo when you can run the following commands in your terminal:
$ rustc --version $ cargo --version
Next, you will need to install the Aiken CLI. You can install the Aiken CLI by running the following command in your terminal:
$ cargo install aiken
Check your installation
You will know you have successfully installed the Aiken CLI when you can run the following command in your terminal:
$ aiken -V
If you face any issues, please check the installation instructions on the Aiken website for more information.
Writing a smart contract with Aiken
In this section, we will walk you through the process of writing a simple smart contract in Aiken. We will also create 2 transactions to lock and unlock assets on the Cardano blockchain.
You can read more about this example on the Aiken website.
Create a new project
First, we will create a new project. Please refer to this guide for more information on creating a new Next.js project.
Next, we create a new Aiken project within this project folder:
$ aiken meshjs/hello_world $ cd hello_world $ aiken check
Remember to check your Aiken project by running `aiken check` after creating a new project and as you develop the contract.
Write the smart contract
Let's create file for our validator, `validators/hello_world.ak`:
use aiken/hash.{Blake2b_224, Hash} use aiken/list use aiken/transaction.{ScriptContext} use aiken/transaction/credential.{VerificationKey} type Datum { owner: Hash<Blake2b_224, VerificationKey>, } type Redeemer { msg: ByteArray, } validator { fn hello_world(datum: Datum, redeemer: Redeemer, context: ScriptContext) -> Bool { let must_say_hello = redeemer.msg == "Hello, World!" let must_be_signed = list.has(context.transaction.extra_signatories, datum.owner) must_say_hello && must_be_signed } }
This validator checks that the redeemer message is "Hello, World!" and that the transaction is signed by the owner of the datum. If both conditions are met, the validator returns `true`. Otherwise, it returns `false`.
Let's compile the smart contract with the Aiken CLI:
$ aiken build
This command will compile the smart contract and generate the `plutus.json` file in the root folder. This file is a CIP-0057 Plutus blueprint, blueprint describes your on-chain contract and its binary interface.
Creating locking transaction
Preparing the frontend
In this section, we will prepare the frontend for our smart contract. We will create a simple UI that allows users to lock and unlock assets on the Cardano blockchain.
Firstly, we need to install the `cbor` package:
$ npm install cbor
Then, we create a folder, `data` and copy the `plutus.json` file into it.
Next, open `pages/index.tsx` and import the following packages:
import { resolvePlutusScriptAddress, Transaction, KoiosProvider, resolveDataHash, resolvePaymentKeyHash, } from "@meshsdk/core"; import type { PlutusScript, Data } from "@meshsdk/core"; import { CardanoWallet, useWallet } from "@meshsdk/react"; import plutusScript from "../data/plutus.json"; import cbor from "cbor";
Importing the contract
We import the contract into our frontend:
const script: PlutusScript = { code: cbor .encode(Buffer.from(plutusScript.validators[0].compiledCode, "hex")) .toString("hex"), version: "V2", }; const scriptAddress = resolvePlutusScriptAddress(script, 0);
Here, we are using the `plutus.json` file to create the script. We are also using the `resolvePlutusScriptAddress` function to resolve the script address.
If you look at it closely, we are encoding with cbor the compiled code of the validator. This is because the validator is encoded in a flat format, which is not the format expected by the cardano-cli and `cardano serialization library`.
Locking assets
We create the transactions to lock assets on the Cardano blockchain:
const hash = resolvePaymentKeyHash((await wallet.getUsedAddresses())[0]); const datum: Data = { alternative: 0, fields: [hash], }; const tx = new Transaction({ initiator: wallet }).sendLovelace( { address: scriptAddress, datum: { value: datum }, }, "5000000" ); const unsignedTx = await tx.build(); const signedTx = await wallet.signTx(unsignedTx); const txHash = await wallet.submitTx(signedTx);
Here, we are creating a new transaction to lock assets on the Cardano blockchain. We are using the `resolvePaymentKeyHash` function to resolve the payment key hash of the wallet. We are also using the `sendLovelace` function to send lovelace to the script address.
As the contracts requires the owner's address in the datum field, we are creating a new datum with the owner's address. We are then using the `build` function to build the transaction, the `signTx` function to sign the transaction, and the `submitTx` function to submit the transaction to the Cardano blockchain.
Unlocking assets
Next, we create the transactions to unlock assets.
First, we create a useful function to retrieve the UTXO of the locked assets:
async function _getAssetUtxo({ scriptAddress, asset, datum }) { const utxos = await koios.fetchAddressUTxOs(scriptAddress, asset); const dataHash = resolveDataHash(datum); let utxo = utxos.find((utxo: any) => { return utxo.output.dataHash == dataHash; }); return utxo; }
And here are the codes to create the transactions to unlock assets:
const scriptAddress = resolvePlutusScriptAddress(script, 0); const address = (await wallet.getUsedAddresses())[0]; const hash = resolvePaymentKeyHash(address); const datum: Data = { alternative: 0, fields: [hash], }; const assetUtxo = await _getAssetUtxo({ scriptAddress: scriptAddress, asset: "lovelace", datum: datum, }); const redeemer = { data: { alternative: 0, fields: ['Hello, World!'] } }; // create the unlock asset transaction const tx = new Transaction({ initiator: wallet }) .redeemValue({ value: assetUtxo, script: script, datum: datum, redeemer: redeemer, }) .sendValue(address, assetUtxo) .setRequiredSigners([address]); const unsignedTx = await tx.build(); const signedTx = await wallet.signTx(unsignedTx, true); const txHash = await wallet.submitTx(signedTx);
Here, we are creating a new transaction to unlock assets on the Cardano blockchain. We are using the `resolvePlutusScriptAddress` function to resolve the script address. We are also using the `resolvePaymentKeyHash` function to resolve the payment key hash of the wallet.
As the contracts requires the owner's address in the datum field, we are creating a new datum with the owner's address. We are then using the `_getAssetUtxo` function to retrieve the UTXO of the locked assets. We are then using the `redeemValue` function to redeem the locked assets, the `sendValue` function to send the assets to the owner's address, and the `setRequiredSigners` function to set the required signers.
As the validator requires `Hello, World!` as the redeemer message, we are creating a new redeemer with the message `Hello, World!`. We are then using the `build` function to build the transaction, the `signTx` function to sign the transaction, and the `submitTx` function to submit the transaction to the Cardano blockchain.
You can check the full code on GitHub.