Advanced
October 18, 2022

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

Install Aleo

$ cargo install --path .

Check if it works:

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.

Program creation

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

You will get the output;

⏳ 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:

Account A:

aleo account new

Make sure to save the save your account details

  Private Key  APrivateKey1zkpAQpLeFgujVMkMEVKXhotR9XVa8B8nGfugMXYXHMdeHnN
     View Key  AViewKey1f6ZSnCkgsatCTDDSX5UgXXfjR14pyQ8oizxE5QGcWTxB
      Address  aleo1nmjzszxsejh0ec6s9tgdx03g24l8e3ev0jy6fx2ckz7s4vt7hgxqu8hz04

Account B:

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 keyis 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 Keythe 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 .

It should look like this:

{
    "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, transactionsand signature.

Getting a block by height

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/8will 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 key ViewKey.
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 key ViewKey.
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 key ViewKey.
curl --location --request GET 'localhost:4180/testnet3/records/unspent' -H 'Content-Type: application/json' -d '"AViewKey1iAf6a7fv6ELA4ECwAth1hDNUJJNNoWNThmREjpybqder"'

Great Job!