Starter Pack For Aleo Developers
In this post, we will review the different tools available in the Aleo SDK to get started developing on Aleo.
The Aleo SDK is a set of tools that makes development on Aleo easy and developer-friendly. With this tool, you can create new accounts, structures (also known as programs in the Aleo ecosystem), create transactions and send them to the network.
1. Environment setup
To get started with Aleo, you will need the Aleo CLI available in your environment. You will also need the rust build (v1.62 or later) to build the Aleo SDK. If you haven't installed it yet, just run the following in a terminal:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
If you only need to update the version of rust, you can run the command:
rustup update stable
You must update your environment variables to be able to use Rust at this stage. You can do this by simply closing the terminal and opening it again.
Next, to install the Aleo SDK, run the program:
Download the source code
git clone https://github.com/AleoHQ/aleo && cd aleo
$ cargo install --path .
aleo -h
You will get the following output:
aleo The Aleo Team <[email protected]> USAGE: aleo [OPTIONS] <SUBCOMMAND> OPTIONS: -h, --help Print help information -v, --verbosity <VERBOSITY> Specify the verbosity [options: 0, 1, 2, 3] [default: 2] SUBCOMMANDS: account Commands to manage Aleo accounts build Compiles an Aleo program clean Cleans the Aleo package build directory help Print this message or the help of the given subcommand(s) new Create a new Aleo package node Commands to operate a local development node run Executes an Aleo program function update Update Aleo
2. Building a simple program
In this step, we will create a simple Aleo program to transfer our own tokens. To do this, we will define our own entry called token and write a function to transfer these tokens between accounts.
Let's start a new Aleo project:
aleo new token cd token
This will create our template to start coding your new Aleo program called token.
For now, we'll fill our main.aleo file with the following code:
// The 'token.aleo' program. program token.aleo; // Our own defined record called token record token: owner as address.private; gates as u64.private; amount as u64.private; // function to mint a new token function mint_token: // token address input r0 as address.private; // token amount input r1 as u64.private; // password input r2 as field.private; // checks if the password is correct hash.psd2 r2 into r3; // it will continue running only is the password hashes match // the execution will fail otherwise. assert.eq r3 7202470996857839225873911078012225723419856133099120809866608931983814353616field; // create a new token in r2 register cast r0 0u64 r1 into r4 as token.record; // return the new token output r4 as token.record; // Function to transfer tokens between accounts function transfer_token: // sender token record input r0 as token.record; // receiver address input r1 as address.private; // amount to transfer input r2 as u64.private; // final balance for sender sub r0.amount r2 into r3; // final balance for receiver add 0u64 r2 into r4; // sender token record after the transfer cast r0.owner r0.gates r3 into r5 as token.record; // receiver token record after the transfer cast r1 0u64 r4 into r6 as token.record; // sender new token record output r5 as token.record; // receiver new token record output r6 as token.record;
These lines of code are called Aleo instructions. Aleo instructions are a kind of assembly language for the Aleo snarkVM virtual machine (the virtual machine where our programs will run), but with rather high-level features such as a typing system, complex instructions for hashing, and so on.
You can read more about these instructions in this post: Getting started with Aleo instructions.
Let's create our Aleo program:
aleo build
⏳ Compiling 'token.aleo'... • Loaded universal setup (in 1459 ms) • Built 'transfer' (in 23549 ms) ✅ Built 'token.aleo' (in "/[...]/token")
Note:
If this is your first time creating an Aleo program, you may have to wait for the "universal setup" to load. Don't panic if you see a different result the first time!
Once built, in the / build directory you will find 3 files:
- main.avm : The file containing your compiled code.
- 2 files for each function in your main.aleo file :
- function_name.prover : Prover for your functions
- function_name.verifier : verifier for your functions
These files will be very important when deploying our application.
3. Account creation
We will create two Aleo accounts for work and transfers:
aleo account new
Make sure to save the save your account details
Private Key APrivateKey1zkpAQpLeFgujVMkMEVKXhotR9XVa8B8nGfugMXYXHMdeHnN View Key AViewKey1f6ZSnCkgsatCTDDSX5UgXXfjR14pyQ8oizxE5QGcWTxB Address aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04
aleo account new
Also ensure to save these details as well.
Private Key APrivateKey1zkpFoVnVMTGvYKRALnzHkU9nKbMZ9Ueu9ZdsouvdmZzEmhh View Key AViewKey1jf1aLTv7mu1zQ8FyNK4So6VrVoE5rk2xT7jywzS9Nptq Address aleo13tny7lhjh7ckjm6ettxvzvdhj856dq6s657nef0p9h9gfruyavzqftkscg
We will use an A account for sending and an B account for receiving.
In Aleo, each account has several associated keys:
They can be thought of as a pair of keys in an asymmetric cryptographic system, where Private key
is the private key of the pair and the other is the public key that derives from it. In this sense Address
, this is the public key that identifies the account, and View Key
the public key that allows you to decrypt the records belonging to the account address (to anyone who knows it).
4. Launching our program
To run our token program, we need to first set up the program.json file.
Update the private_key and development address to the address of the newly created A account .
{ "program": "token.aleo", "version": "0.0.0", "description": "", "development": { "private_key": "APrivateKey1zkpAQpLeFgujVMkMEVKXhotR9XVa8B8nGfugMXYXHMdeHnN", "address": "aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04" }, "license": "MIT" }
This is important because in terms of Aleo execution, the A account will be the one running the program to send tokens to the B account . This has some security implications, as you can only enter entries when running a program owned by the account specified in program.json , as we'll see in a second.
First we need to run the mint function to generate the initial number of tokens owned by A . This function requires a password to display the entry with the specified number. If the given password is incorrect, the execution will fail, thus avoiding the generation of tokens.
The password for this example is:
3634422524977942384127113436866104517282080062207687912678345956934082270693field
We need to run the following command:
aleo run mint_token aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04 100u64 3634422524977942384127113436866104517282080062207687912678345956934082270693field
and we get the following token entry:
• Loaded universal setup (in 1472 ms) 🚀 Executing 'token.aleo/mint_token'... • Executing 'token.aleo/mint_token'... • Executed 'mint_token' (in 2174 ms) ➡️ Output • { owner: aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04.private, gates: 0u64.private, amount: 100u64.private, _nonce: 3024738992072387217402876176731225730589877991873828351104009809002984426287group.public } ✅ Executed 'token.aleo/mint_token' (in "[...]/token")
Now let's use this notation to transfer 10 tokens from A to B
aleo run transfer_token "{ owner: aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04.private, gates: 0u64.private, amount: 100u64.private, _nonce: 3024738992072387217402876176731225730589877991873828351104009809002984426287group.public }" aleo13tny7lhjh7ckjm6ettxvzvdhj856dq6s657nef0p9h9gfruyavzqftkscg 10u64
and in the output we should see two entries, each representing the current state of our token entries for each account.
• Loaded universal setup (in 1473 ms) 🚀 Executing 'token.aleo/transfer_token'... • Executing 'token.aleo/transfer_token'... • Executed 'transfer_token' (in 3620 ms) ➡️ Outputs • { owner: aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04.private, gates: 0u64.private, amount: 90u64.private, _nonce: 2178231086979351800436660566645386776768595203243256243508388701123108642520group.public } • { owner: aleo13tny7lhjh7ckjm6ettxvzvdhj856dq6s657nef0p9h9gfruyavzqftkscg.private, gates: 0u64.private, amount: 10u64.private, _nonce: 4704084834675699921301424200265500298050115484153136728500446828605991979105group.public } ✅ Executed 'token.aleo/transfer_token' (in "[...]/token")
That's all! We have completed our first token transfer.
As a test case, let's see what happens if we swap two addresses ( A and B ) in the last command aleo run transfer_token
:
aleo run transfer_token "{ owner: aleo13tny7lhjh7ckjm6ettxvzvdhj856dq6s657nef0p9h9gfruyavzqftkscg.private, gates: 0u64.private, amount: 100u64.private, _nonce: 3024738992072387217402876176731225730589877991873828351104009809002984426287group.public }" aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04 10u64
We will get the following result:
• Loaded universal setup (in 1461 ms) ⚠️ Input record for 'token.aleo' must belong to the signer
This tells us that the owner of the input record does not belong to the sender address, which is the address we updated in our program.json .
As you can see, Aleo programs perform several high-level vulnerability checks, which makes our program quite fault-tolerant by default.
5. Deploying our program locally
To run Aleo Node locally, just run the following command inside the program path in another terminal:
aleo node start
Wait a couple of seconds and pay attention to the first results:
⏳ Starting a local development node for 'token.aleo' (in-memory)... • Loaded universal setup (in 1471 ms) • Executing 'credits.aleo/genesis'... • Executed 'genesis' (in 1872 ms) • Loaded universal setup (in 1423 ms) • Verified 'genesis' (in 46 ms) • Verified 'genesis' (in 46 ms) 🌐 Server is running at http://0.0.0.0:4180 📦 Deploying 'token.aleo' to the local development node... • Built 'mint_token' (in 11832 ms) • Certified 'mint_token': 264 ms • Built 'transfer_token' (in 23166 ms) • Certified 'transfer_token': 537 ms • Calling 'credits.aleo/fee'... • Executed 'fee' (in 2616 ms) • Verified certificate for 'mint_token': 89 ms • Verified certificate for 'transfer_token': 143 ms • Verified 'fee' (in 47 ms) • Verified certificate for 'mint_token': 89 ms • Verified certificate for 'transfer_token': 141 ms • Verified 'fee' (in 47 ms) • Verified certificate for 'mint_token': 88 ms • Verified certificate for 'transfer_token': 143 ms • Verified 'fee' (in 46 ms) 🛡️ Produced block 1 (ab1cz4e3zrw8xje7q6gutsjkqktvmwhq0yx3j4tqjeqnfr3c2j0f5gs5hwmf9) { "previous_state_root": "1864323752948026853541213650623314215454919286649566834473029713121712183360field", "transactions_root": "2938300578986025380747616267226890561116633830917000361321232469947771414785field", "metadata": { "network": 3, "round": 1, "height": 1, "coinbase_target": 18446744073709551615, "proof_target": 18446744073709551615, "timestamp": 1660331897 } } ✅ Deployed 'token.aleo' in transaction 'at13pzudena0mk7xrj7m4tyar5x96ynr7m2q30c27lvc3gr7w5rn58q4agsk2' • Executing 'credits.aleo/transfer'... • Executed 'transfer' (in 3336 ms) • Verified 'transfer' (in 47 ms) • Verified 'transfer' (in 47 ms) 🛡️ Produced block 2 (ab1d2cypkhgv7fpardzd8pnn8mq8pwtwk453jyzc36cvt7z9r24vcpskj4tmd) ...
As you can see here, we have a local node running and mining blocks, our token program is already deployed in a transaction, and a server running at http://localhost:4180 is waiting for requests. Pretty amazing, right?
Overview of the most important server endpoints
In this section, we'll cover the most useful HTTP RESTful endpoints that you can call to retrieve information from your node's registry. You can use either a web browser or the curl
.
💡 By default, the node web service listens on port 4180.
Getting the height of the last block
To get your node's latest registry height, you can GET
-query to http://localhost:4180/testnet3/latest/block/height
.
The response contains the value of the last height of your running node.
Getting the hash of the last block
To get the hash of your node's last registry block, you can GET
-query at
http://localhost:4180/testnet3/latest/block/hash
You will see a response containing a hash like this:
"ab14ja24wr9rdg2hmpym35kvw08ywwp6eyhknn23zr3ypwrxvqsjyzqpns7ml"
Getting the last registry block
To get the latest registry block of your node, you can GET
-query at
http://localhost:4180/testnet3/latest/block
You will see a JSON response that contains block attributes.
The root attributes are: block_hash
, previous_hash
, header
, transactions
and signature
.
To get a block at a given height from your node's registry, you can GET
-request at
http://localhost:4180/testnet3/block/{height}
For example: http://localhost:4180/testnet3/block/8
will return a block height of 8.
Getting records related to an account
You can get records in three different ways, depending on what type of records you want to query. To do this, you will need your key ViewKey
. There are two types of records, spent and unspent, we'll talk about them later. You can see the list of related endpoints below:
GET /testnet3/records/all
: This endpoint retrieves all records that belong to the given keyViewKey
.
curl --location --request GET 'localhost:4180/testnet3/records/all' -H 'Content-Type: application/json' -d '"AViewKey1iAf6a7fv6ELA4ECwAth1hDNUJJNNoWNThmREjpybqder"'
GET /testnet3/records/spent
: This endpoint retrieves only spent records that belong to the given keyViewKey
.
curl --location --request GET 'localhost:4180/testnet3/records/spent' -H 'Content-Type: application/json' -d '"AViewKey1iAf6a7fv6ELA4ECwAth1hDNUJJNNoWNThmREjpybqder"'
GET /testnet3/records/all
: This endpoint retrieves only unspent records that belong to the given keyViewKey
.
curl --location --request GET 'localhost:4180/testnet3/records/unspent' -H 'Content-Type: application/json' -d '"AViewKey1iAf6a7fv6ELA4ECwAth1hDNUJJNNoWNThmREjpybqder"'