What is Fi? Fi is an easy on the eyes smart contract language for the Tezos ecosystem. Syntactically similar to javascript, Fi opens the door for more developers to engage in writing smart contracts for Tezos. Fi much like it’s sibling liquidity compiles down to Tezos’s native smart contract language Michelson.

If you haven’t taken a look at Michelson before, check out this program:

parameter bytes;

storage (map address (pair string (pair mutez nat)));

code{DUP;

CDR;

NIL operation;

PAIR;

SWAP;

CAR;

DUP;

PUSH nat 4;

PUSH nat 0;

SLICE;

IF_NONE{PUSH nat 100;

FAILWITH}{};

DUP;

PUSH bytes 0xc3ed8123;

COMPARE;

EQ;

IF{DROP;

DUP;

SIZE;

PUSH nat 4;

SWAP;

SUB;

DUP;

GT;

IF{}{PUSH nat 102;

FAILWITH};

ABS;

PUSH nat 4;

SLICE;

IF_NONE{PUSH nat 101;

FAILWITH}{};

UNPACK (pair string (pair mutez nat));

IF_NONE{PUSH nat 103;

FAILWITH}{};

PAIR;

SENDER;

DIP{DUP;

CAR;

SOME;

};

DIIP{

DUP;

CDDR;

};

UPDATE;

SWAP;

SET_CDDR;

CDR;

}

{DUP;

PUSH bytes 0x65a00f8a;

COMPARE;

EQ;

IF{DROP;

DUP;

SIZE;

PUSH nat 4;

SWAP;

SUB;

DUP;

GT;

IF{}{PUSH nat 102;

FAILWITH};

ABS;

PUSH nat 4;

SLICE;

IF_NONE{PUSH nat 101;

FAILWITH}{};

UNPACK (pair string nat);

IF_NONE{PUSH nat 103;

FAILWITH}{};

PAIR;

NONE (pair string (pair mutez nat));

PAIR;

SENDER;

DIP{DUP;

CDDDR;

};

GET;

IF_NONE{PUSH string "Key not found in map";

FAILWITH

}{};

SWAP;

SET_CAR;

DUP;

CDAAR;

SWAP;

SET_CAAR;

DUP;

CDADR;

SWAP;

SET_CADDR;

SENDER;

DIP{

DUP;CAR;

SOME;

};

DIIP

{

DUP;CDDDR;

};

UPDATE;

SWAP;

SET_CDDDR;

CDDR;

}

{DROP;

PUSH nat 400;

FAILWITH;

}

}

};

As you can see it’s not the easiest to follow. There’s a reason Tezos uses Michelson, and it’s because it opens the door for formal verification in a stack like language. Michelson, however, does not offer a coding syntax that most people are familiar with. We’re in luck though, Fi seeks to bridge that gap!

So Let’s get building!

We are going to build the same Michelson program above in Fi, compile it down to Michelson using Fi’s compiler, initialize the contract on Tezos, and interact with with the contract.

Let’s start by defining what our program does. The above program allows you to store information about a person including their name, balance (mutez), and age. Each person is mapped by the senders address (address of the KT1 address interacting with the contract).

Let’s first represent what a person is in Fi, by defining the Person object as below.

struct Person(

string name,

mutez balance, // The mutez type is a positive natural number

nat age // Natural number, think integer

);

Now we need to let the Tezos blockchain know that we are going to be storing information about these persons.

storage map[address=>Person] users;

The above storage statement tells Tezos that we intend to store Person(s) by the address of the sender in the form of a map. Maps should be a familiar concept to any developer, but it’s essentially storing a value that can be retrieved by a key. In this case the Person is the value and the address is the key.

The next step is to create a way to interact with the storage of Person(s). Let’s create the ability to add a person. To interact with a smart contract in Tezos, there are things called entry points. You can think of them as public functions, exposed for anyone to call. In Fi, the keyword used for an entry point is entry.

entry add(Person person){

storage.users.push(SENDER, input.person);

}

Above we created an entry point called add, which takes a parameter Person to store. We then take the storage and push the person, and the sender’s (invoker’s) address to the map we defined before.

So now that we can interact with the smart contract by adding a person, we should also have the ability to update a person’s age and name if that person already exists in storage.

entry update(string newName, nat newAge){

let Person me = storage.users.get(SENDER);

me.name = input.newName;

me.age = input.newAge;

storage.users.push(SENDER, me);

}

The first thing we do in this entry, is get the Person associated with the sender. The next thing we do is assign the new name and age to that person. And finally we put back the Person into storage.

Putting it all together:

struct Person(

string name,

mutez balance,

nat age

); storage map[address=>Person] users; entry add(Person person){

storage.users.push(SENDER, input.person);

} entry update(string newName, nat newAge){

let Person me = storage.users.get(SENDER);

me.name = input.newName;

me.age = input.newAge;

storage.users.push(SENDER, me);

}

Tada! You have written your first Tezos smart contract in Fi. Now what? Let’s run it of course!

Compiling Fi

Let’s start by cloning the Fi-Compiler.

Next you will need to install NPM on your system. Once you’ve installed NPM, you can now install the fi-compiler.

npm i -g fi-cli

After running the above install command you will now find the fi program in the same directory as you cloned. In order to use it, create a file called person.fi, and store the code we wrote previously. Then we will use the fi program to compile that file into Michelson.

fi compile person.fi

You will then see the creation of two files person.fi.abi, and person.fi.ml. For now we will ignore the person.fi.abi file and focus on the person.fi.ml. We will come back to person.fi.abi later.

Now that we compiled our Michelson program from Fi, let’s run it using your Tezos node. We can do this by the following command.

./tezos-client originate contract person for wallet transferring 0 from wallet running person.fi.ml --init '{Elt "tz1gH29qAVaNfv7imhPthCwpUBcqmMdLWxPG" (Pair "Jackson" (Pair 100000 23))}' --burn-cap 1.297

Make sense? Probably not, or at least not all of it. Basically all the above command does is create the contract person (arbitrary name) who’s owner is a local tz1 address, in this case wallet. We then transfer 0 XTZ into the contract from wallet (because this contract does not need to store XTZ), and then point the client to the person.fi.ml file.

Lastly we initialize the contact with a person Jackson who has 100000 mutez, and is of age 23. Jackson is stored in as a value in the storage map and is retrieved by his key address tz1gH29qAVaNfv7imhPthCwpUBcqmMdLWxPG.

Next, let’s take a look on how to interact with the smart contract, in a developer friendly way.

Working with Fi and NodeJs

Previously I mentioned a file with the extension *.abi. We’re going to be working with that file shortly, but first we’re going to see the capabilities of the fi-compiler package for node.

Start by installing the fi-compiler module.

npm install fi-compiler

Now let’s write a NodeJS program that compiles our Fi Program, and allows us to generate byte inputs for our contract.

var fi = require("fi-compiler"); // 1. var ficode = ` // 2.

struct Person(

string name,

mutez balance,

nat age

); storage map[address=>Person] users; entry add(Person person){

storage.users.push(SENDER, input.person);

} entry update(string newName, nat newAge){

let Person me = storage.users.get(SENDER);

me.name = input.newName;

me.age = input.newAge;

storage.users.push(SENDER, me);

}

`; var compiled = fi.compile(ficode); // 3. fi.abi.load(compiled.abi); // 4. var input = { // 5.

person : {

name : "Jackson",

balance : 100000,

age : 23

}

}; console.log(fi.abi.entry("add", input)); // 6.

Let’s walk through this program step by step:

Import the fi-compiler module. Store our fi program into a variable called ficode. Compile the ficode and store the result into compiled. As a result of compiling the fi code, the variable compiled now has access to abi. Abi is a helper definition to define types into Michelson primitives. Now we create the input values in a json structure that we are going to use to call the add function. We then print out a Michelson readable byte representation to be used as input to call our smart contract.

By running this program, you should see the following output:

0xc3ed812305070701000000074a61636b736f6e070700909c010017

You can then use the above bytes to add the person defined in the NodeJs application. We’ll invoke the add function below.

Adding People to the Contract

./tezos-client transfer 0 from wallet to person --arg '0xc3ed812305070701000000074a61636b736f6e070700909c010017' --burn-cap 0.002

As you can see in the above command we again transfer 0 tez, because no value will be stored in the contract. We then make a call to our contract person with the argument add (in the bytes). One thing to note here, invoking an entry point in a contract isn’t free, so unfortunately we’re going to have to burn some XTZ.

Finally, let’s check the storage of the contract to see if we were successful.

./tezos-client get script storage for person

The above command produces the following output:

{ Elt "tz1gH29qAVaNfv7imhPthCwpUBcqmMdLWxPG" (Pair "Jackson" (Pair 10000 23)) }

Voilà! You’ve successfully added a person to the smart contract.

Conclusion

Congratulations if you’ve made it this far, I know it’s not always easy. Luckily Fi makes Tezos smart contracts much more manageable for the average Joe. Just as a recap on what we learned:

How to write a simple smart contract in Fi. How to compile Fi into Michelson. How to run you’re contract on Tezos. How to make your contract more manageable through the fi-compiler module for Nodejs. How to interact with your smart contract.

I hope you enjoyed this tutorial, there are many more to come. This tutorial was sponsored and created by TezTech, a Tezos company created by Stephen Andrews.

Resources