Introduction

Factori — the key Tezos dApps development framework

Welcome to Factori, a tool designed around the needs of Tezos smart contract & dApp developers based on our long experience in this domain. Factori's mantra is low code. Its primary goal is to automate, as much as possible, boilerplate code generation so that you can focus on writing, testing, and deploying your smart contracts.

The figure below summarizes Factori's main features: given Michelson smart contracts (whether you wrote them yourself or found them on a Tezos network), it automates the following tasks:

  • SDKs generation in various languages, like OCaml, Typescript, Python, and C#;
  • Basic Web dApp generation to graphically interact with the contracts;
  • Plugins generation for Crawlori and Dip dup crawlers to easily index the calls to the smart contracts into a relational database;
  • A powerful scenario language to write tests;
  • Factori will also provide a light static analysis module for Michelson contracts (e.g., detection of entrypoints permissions and unused storage fields, use of sets/maps instead of big maps).

Factori features

Factori's long-term goal is to provide an equivalent of Truffle and Ganache tools for Tezos' native smart contracts language, with an additional focus on meta-programming and automatic code generation.

Next step: quick start!

When you use Factori, you will have a working directory, which we will sometimes call the Factori project directory. Whenever we give relative paths (.e.g src/python_sdk), the will implicitly always be given relative to this working directory.

Let's move to the installation process. For that, we highly advise the Docker approach.

Quickstart

Generating a SDK

Factori can generate SDKs in several languages: Typescript, OCaml, C# and Python.

Generating a web interface (simple Dapp)

Factori can also generate a Web Interface for viewing and interacting with your smart contract using any standard wallet.

Generate a (contract-specific) configuration for a blockchain crawler

Factori can also generate code for the use of blockchain crawlers such as DipDup or Crawlori.

Installation

Factori could be installed in 2 ways.

Installation using Docker

Factori script for Docker

The simplest way to use Factori via docker is via this script. Get it with:

wget https://gitlab.com/functori/dev/factori/-/snippets/2509259/raw/main/factori.sh -O factori

Then, make it executable with:

chmod +x factori

To call factori without a path prefix, move it to, e.g., $HOME/bin/ (assuming $HOME/bin/ appears in $PATH):

mv factori $HOME/bin/

Now try:

factori

If you didn't pull any factori image yet and 0.6.1 is the latest release, you will get something like:

Warning: It seems that you didn't pull any factori image for the moment.
The tag of the latest release is 0.6.1. Consider downloading it with docker pull registry.gitlab.com/functori/dev/factori:0.6.1.

Let's pull the indicated docker image:

docker pull registry.gitlab.com/functori/dev/factori:0.6.1

Again, run:

factori

Now, it should print something like:

Notice: using the latest pulled factori version 0.6.1.
Use 'factori --help' for help on commands

You are ready to use factori! You can move to SDK generation section.

Versions management with the script

The script above can be parameterized via a VERSION environnement variable. For instance, to use the docker image of the release 0.6.1, you can write:

VERSION=0.6.1 factori <CMD>

Of course, if you didn't pull version 0.6.1 yet, you'll be asked to do it or to pull the latest released version.

In addition to the tag of a released/pulled version, the value of VERSION environnement variable can be:

  • latest, to use the latest docker image release;
  • show-pulled, to show the list of pulled factori images;
  • show-latest-release, to show the tag of the latest factori release.

Note that the latest release that has been pulled is systematically used when factori is invoked with:

factori <CMD>

VERISON=next can be used to target development versions of Factori.

Docker pull

If you don't want to use the script above, you have to pull the docker images yourself. For instance, you can get the image with the latest with:

docker pull registry.gitlab.com/functori/dev/factori:latest

The list of released factori version is available via gitlab container registry. If you would like to use a previous version, e.g. 0.3.1, you can run:

docker pull registry.gitlab.com/functori/dev/factori:0.6.1

Then, you should be able to run factori as follows (probably with some extra arguments depending on the usage):

docker run registry.gitlab.com/functori/dev/factori:latest

From source

To install Factori from source code you need to have opam installed in your machine.

Opam

Check this link to install opam.

Clone

Clone the repository:

git clone https://gitlab.com/functori/dev/factori.git
cd factori

(Optional) Opam switch

If you want to have a local opam switch for factori, just run:

make opam-switch

Dependencies

Once opam installed, install all dependencies by running:

make build-deps

Build

To build factori binaries, run:

make

Install

You can install Factori by using:

make install

This will, by default, copy the binary factori.asm to ~/.local/bin/factori. If you want to customize the installation just edit the Makefile.

SDKs generation

This section demonstrates the generation and use of SDKs through a simple example in the following programming languages:

We assume you are somewhat familiar with the development environment of your chosen language above (node and npm for Typescript, opam for OCaml, etc.).

Before going into more details, let's introduce the example: we will use a contract we deployed on Ghostnet to illustrate the use of the various SDKs generated by Factori. Its address is KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of.

All you need to know about the smart contract is that it has two entrypoints:

  • hello: it takes a string and puts it in the contract's storage, in addition to some other updates;
  • ping: is a NOP, as it doesn't update the contract's storage.

For reference, the corresponding Camligo code is provided below. Note that you don't need to read or understand it to be able to play with the SDKs.

#![allow(unused)]
fn main() {
(* This the contract's storage type. It is made of:
   - a [count] field of type nat;
   - a [date] field of type timestamp;
   - a [msg] field of type string. *)
type storage = {
    count : nat;
    date : timestamp;
    msg: string;
}

(* The type action has two cases, from which two entrypoints
   for the contract are derived:
   - A [ping] entrypoint, that takes a unit parameter;
   - A [hello] entrpypoint, that takes a string parameter. *)
type action = Ping | Hello of string

(* This is the main entry of the smart contract. *)
let main (param, storage : action * storage) =
  let ops =  ([] : operation list) in
  match param with
  | Ping -> (* [ping] enptrypoint is a NOP. *)
    ops, storage
  | Hello msg ->
    (*  [hello] entrypoint puts the message in the storage,
       and updates the counter and the date. *)
    ops, { count = storage.count + 1n; msg = msg; date = Tezos.get_now () }
}

The corresponding Michelson code is:

{ parameter (or (string %hello) (unit %ping)) ;
  storage (pair (pair (nat %count) (timestamp %date)) (string %msg)) ;
  code { UNPAIR ;
         NIL operation ;
         SWAP ;
         IF_LEFT
           { NOW ; PUSH nat 1 ; DIG 4 ; CAR ; CAR ; ADD ; PAIR ; PAIR }
           { DROP ; SWAP } ;
         SWAP ;
         PAIR } }

For a more complete documentation, go to the features > SDK generation section.

Typescript SDK

Let's generate a Typescript SDK for KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of and interact with it. See section SDK generation for the details about this contract. The Typescript SDK relies on the Taquito library for forging and signing operations.

Import a KT1 from the Tezos blockchain

To generate the SDK of the contract in the current directory, run:

factori import kt1 . KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of \
    --typescript \
    --name my_contract \
    --network ghostnet \
    --force

Compile deps and SDK

You will need to have Node installed.

Then you want to install the necessary Typescript dependencies, compile your SDK, and cleanly format the generated code:

make ts-deps
make format-ts # optional but recommended
make ts

You are ready to use the Typescript SDK!

Hello world example

First, you may want to have a look at the content of src/typescript_sdk/src/ directory.

ls src/typescript_sdk/src/

Create a file src/typescript_sdk/src/test.ts with the following content:

import * as C from "./my_contract_interface"
import * as functolib from "./functolib"
import {
    TezosToolkit,
  } from "@taquito/taquito"

var config = functolib.ghostnet_config;

const tezosKit = new TezosToolkit(config.node_addr)
const debug = false

async function main(tezosKit: TezosToolkit){
    functolib.setSigner(tezosKit, functolib.alice_flextesa.sk);
    let hello_kt1 = "KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of"
    let res = await C.call_hello(tezosKit, hello_kt1, "Hello from typescript", 0, "", true);
    console.log("Operation sent to a node. Its hash is: " + res.hash);
    console.log("Check its status: https://ghostnet.tzkt.io/" + res.hash);
    // TODO: Add a await inclusion here.
    return;
}

main(tezosKit)

Recompile with

make ts

Run with

node src/typescript_sdk/dist/test.js

If everything goes well, you should see your transaction at: https://ghostnet.tzkt.io/KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of

Quick Deploy

If you want to quickly re-originate the contract, you can run

factori deploy --typescript --network ghostnet --storage blockchain my_contract

This will deploy the contract on Ghostnet, using Flextesa's Alice account, and with as initial storage the storage on the blockchain at the moment of importation.

Remark

If you run into the error

TypeError: Do not know how to serialize a BigInt
    at JSON.stringify (<anonymous>)

when using JSON.stringify in your code, the file functolib.ts exports a variable JSONbig which has a stringify method that will solve this error (thanks to the json-bigint library). You can hence simply use JSONbig.stringify.

Note that, if no language is specified, the SDK generated by Factori is the Typescript one.

Python SDK

Let's generate a Python SDK for KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of and interact with it. See section SDK generation for the details about this contract. The Python SDK relies on the Pytezos library for forging and signing operations.

Import a KT1 from the Tezos blockchain

To generate the SDK of the contract in the current directory, run:

factori import kt1 . KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of \
    --python \
    --name my_contract \
    --network ghostnet \
    --force

Compile deps and SDK

You want to install the necessary Python dependencies:

pip install pytezos mypy pyright black

Some of these packages may be installed in $HOME/.local/bin; check that it is in your path.

Now cleanly format the generated code:

make format-python

You are ready to use the Python SDK!

Hello world example

First, you may want to have a look at the content of src/python_sdk/ directory.

ls src/python_sdk/

Create a file src/python_sdk/test.py with the following content:

from importlib.metadata import metadata
import time
import my_contract_python_interface
import blockchain

def main():
    debug = False
    hello_kt1 = "KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of"

    hello_param: my_contract_python_interface.Hello = "Hello from Python"
    ophash = my_contract_python_interface.callHello(
        hello_kt1,
        _from=blockchain.alice,
        param=hello_param,
        networkName="ghostnet",
        debug=debug,
    )
    print("A hello operation is sent to a node. Its hash is: " + ophash)
    print("Waiting for its inclusion in a block ...")
    time.sleep(15)  # we need to wait for one block before calling the contract
    print("Check the status of your operation: https://ghostnet.tzkt.io/" + ophash)

main()

To run it, simply run:

python3 src/python_sdk/test.py

If everything goes well, you should see your transaction at: https://ghostnet.tzkt.io/KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of

OCaml SDK

Let's generate a OCaml SDK for KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of and interact with it. See section SDK generation for the details about this contract. The OCaml SDK relies on the tzfunc library for forging and signing operations.

Import a KT1 from the Tezos blockchain

To generate the SDK of the contract in the current directory, run:

factori import kt1 . KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of \
    --ocaml \
    --name my_contract \
    --network ghostnet \
    --force

Compile dependencies

You want to install the necessary OCaml dependencies. You need to be inside an opam switch, or you can create one with

make _opam

Then install needed dependencies:

opam update
make deps

Now cleanly format the generated code:

make format-ocaml

You are ready to use the OCaml SDK!

Hello world example

First, you may want to have a look at the content of src/ocaml_sdk/ and src/ocaml_scenarios/ directories:

tree src/ocaml_sdk/ src/ocaml_scenarios/

Edit the file src/ocaml_scenarios/scenario.ml with the following content:

module M = My_contract_ocaml_interface
module B = Blockchain

let main () =
  let open Tzfunc.Rp in
  let hello_kt1 = "KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of" in
  let>? oph =
    M.call_hello
      ~from:B.alice_flextesa
      ~kt1:hello_kt1
      ~node:B.ghostnet_node
      "Hello from OCaml"
  in
  Format.eprintf "A hello operation was sent to the node, with hash %s@." oph ;
  Format.eprintf
    "Check the status of your operation: https://ghostnet.tzkt.io/%s@."
    oph ;
  Lwt.return_ok oph

let _ = Tzfunc.Node.set_silent true

let _ = Lwt_main.run @@ main ()

To run it, simply run:

make run_scenario_ocaml

If everything goes well, you should see your transaction at: https://ghostnet.tzkt.io/KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of

Quick Deploy

If you want to quickly re-originate the contract, you can run

factori deploy --ocaml --network ghostnet --storage blockchain my_contract

This will deploy the contract on Ghostnet, using Flextesa's Alice account, and with as initial storage the storage on the blockchain at the moment of importation.

C# SDK

Let's generate a C# SDK for KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of and interact with it. See section SDK generation for the details about this contract. The C# SDK relies on the Netezos library for forging and signing operations.

Import a KT1 from the Tezos blockchain

To generate the SDK of the contract in the current directory, run:

factori import kt1 . KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of \
    --csharp \
    --name my_contract \
    --network ghostnet \
    --force

Compile deps and SDK

You need to have dotnet and the Netezos package installed. For resources, look at:

  • Dotnet
  • https://netezos.dev/
  • https://github.com/baking-bad/netezos.

now initiate the C# project and cleanly format the generated code:

make csharp-init
make format-csharp

You are ready to use the C# SDK!

Hello world example

First, you may want to have a look at the content of src/csharp_sdk/ directory.

ls src/csharp_sdk/

Edit the file src/csharp_sdk/Program.cs with the following content:

using static Blockchain.Identities;

async static Task main()
{
    var hello_kt1 = "KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of";

    var ophash = await my_contract.Functions.CallHello(aliceFlextesa, hello_kt1, "Hello from C#", 1000000, 100000, 30000, 1000000, "ghostnet", false);
    Console.WriteLine($"A hello operation is sent to a node. Its hash is: {ophash}");
    Console.WriteLine("Waiting for its inclusion in a block ...");
    await Task.Delay(15000); // wait 15 seconds
    Console.WriteLine($"Check the status of your operation: https://ghostnet.tzkt.io/{ophash}");
}

await main();

To run it, simply run:

dotnet run --project src/csharp_sdk/

If everything goes well, you should see your transaction at: https://ghostnet.tzkt.io/KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of

Crawler configuration generation

Quickstart generating an SDK for Crawlori or Dipdup:

Dipdup SDK

Let's generate a configuration for the DipDup crawler.

For this tutorial, you will need Poetry(it is a dependency of DipDup). One way to install it is

curl -sSL https://install.python-poetry.org | python3 -

Import KT1 from the blockchain

Start by generating an SDK for the contract in the current directory, with the extra --dipdup option. If no option is given for the language, the Typescript SDK is generated:

factori import kt1 . KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of \
    --dipdup \
    --name my_contract \
    --network ghostnet \
    --db-name tmp \
    --force

Note: if you change the db-name option, make sure it does not contain any special characters except _.

Look at the code

First, you may want to have a look at the content of src/dipdup/src/ directory.

tree src/dipdup/
src/dipdup/
├── dipdup.json
├── dipdup.yml
├── handlers
│   ├── on_my_contract_hello.json
│   ├── on_my_contract_hello.py
│   ├── on_my_contract_new_storage.json
│   ├── on_my_contract_new_storage.py
│   ├── on_my_contract_ping.json
│   └── on_my_contract_ping.py
├── models.json
└── models.py

First, get dipdup as follows:

poetry init --python ">=3.10,<3.11" -n
poetry add dipdup
poetry shell

Factori has generated a configuration file (dipdup.yml) for you. This configuration file will make DipDup indexer track every transactions calling your contract and the big_map changes.

You should copy the ./src/dipdup/dipdup.yml file

cp src/dipdup/dipdup.yml ./

Check that the field url in dipdup.yml is

    url: https://api.ghostnet.tzkt.io/

Change it if it isn't.

The next step is:

dipdup init

This will create the python package and its structure in a tmp folder.

cp src/dipdup/models.py src/dipdup/handlers/*.py src/tmp/

Then you should be able to run it with:

dipdup run

Crawlori SDK

Let's generate a configuration for our Crawlori blockchain indexer. Crawlori requires a bit more setup than the other quick start tutorials, but it's well worth the (small) hassle! It allows to crawl Tezos blockchains in a very fast and modular way.

Import KT1 from the blockchain

Start by generating an SDK for the contract in the current directory, with the extra --crawlori option. If no option is given for the language, the Typescript SDK is generated:

factori import kt1 . KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of \
    --crawlori \
    --name my_contract \
    --network ghostnet \
    --force

Before we go further, you need to do some setup.

Setting things up

Postgresql setup

In the following, <USER> is the user of your shell (typically you can obtain it through the $USER variable.

If this is your first time using pgocaml, you might need to install postgresql and configure it to give the required rights to your user.

sudo -i -u postgres -- psql -c 'create user <USER> with createdb;'

Opam setup

Warning: if you run into problems, check that you have disabled sandboxing as indicated at the end of this section.

You want to install the necessary OCaml dependencies. You need to be inside an opam switch, or you can create one with

make _opam

Then install needed dependencies:

opam update
make deps

Note: even if you already did this previously in the OCaml quickstart, you still need to re-do it because the dependencies for Crawlori are slightly bigger.

Now cleanly format the generated code:

make format-ocaml

The pgocaml package doesn't play well with the sandbox mode of OPAM. So you might need to disable it.

On a fresh install of opam you can proceed like this:

opam init --reinit --disable-sandboxing --bare
opam reinstall pgocaml pgocaml_ppx ez_pgocaml

Otherwise, you can edit the opam config file (default location is ~/.opam/config). To disable the sandboxing you can comment out the last three lines:

wrap-build-commands:
  ["%{hooks}%/sandbox.sh" "build"] {os = "linux" | os = "macos"}
wrap-install-commands:
  ["%{hooks}%/sandbox.sh" "install"] {os = "linux" | os = "macos"}
wrap-remove-commands:
  ["%{hooks}%/sandbox.sh" "remove"] {os = "linux" | os = "macos"}

If you edited ~/.opam/config as said above, you will need to reinstall pgocaml:

opam reinstall pgocaml

Building the crawler

make crawlori

Hello world example

Let's generate a Crawlori plugin for KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of. See section SDK generation for the details about this contract.

Look at the code

First, you may want to have a look at the content of src/typescript_sdk/src/ directory.

ls src/ocaml_crawlori/

Edit the configuration file

Edit the file src/ocaml_crawlori/config.json:

{ "nodes": [
    "https://rpc.tzkt.io/ghostnet/"
  ],
  "sleep": 1,
  "start": 3026972,
  "confirmations_needed": 2,
  "step_forward": 30,
  "register_kinds": [],
  "originator_address" : "tz1XcgTCbVj8nbJb3DhrsYjNtjVdouZHMmYb"
 }

Then to start crawlori, you can run the following command:

_build/default/src/ocaml_crawlori/crawler.exe src/ocaml_crawlori/config.json

Web Interface

Factori can generate a web interface from a KT1:

factori import kt1 . KT1Fjiwsmo49yM7jch6KG4ETLFkatF3Qj8of \
    --web \
    --name my_contract \
    --network ghostnet \
    --force

To generate the web page, run:

make ts-deps
make web

and click on the link to the local web page.

Linting & Analysis

Quickstart linting or analyzing a Michelson Smart Contract:

Linting

Lint a Michelson file

Lint a contract using its Michelson compiled form. It can be in either a JSON (.json) or normal Michelson format (.tz).

factori lint michelson [MICHELSON_FILE]

##Lint KT1

Lint an on-chain contract using its KT1.

factori lint kt1 [OPTION] [KT1]
  • --network=network
    • Specify on which network (mainnet,ithacanet,etc...) you would like to perform the operation. Any prefix of more than three letters "mai", "main", "gho" will work.

Note that the Michelson file has a better printing interface because of the possibility to print the exact location of the errors.

Analysis

Analyze a Michelson file

Analyze a contract using its Michelson compiled form. It can be in either a JSON (.json) or normal Michelson format (.tz).

factori analyze michelson [MICHELSON_FILE]

Analyze a KT1

Analyze an on-chain contract using its KT1.

factori analyze kt1 [OPTION] [KT1]
  • --network=network
    • Specify on which network (mainnet,ithacanet,etc...) you would like to perform the operation. Any prefix of more than three letters "mai", "main", "gho" will work.

Features

In this section, we give an overview of Factori's features. (Note that the next section gives quick tutorials for try out each feature.) Here is a list of subsections of the current section:

SDKs generation

Automatic code generation

Factori's first and foremost feature is the automatic generation of a static interface, or SDK (Software Development Kit), from a smart contract. This smart contract can be retrieved from a Tezos blockchain (mainnet, ghostnet, flextesa, etc...) or directly from its Michelson code. The interface may be generated once and for all, for instance if you are trying to interact with a fixed, on-chain contract, or it can be generated on the fly as many times as needed, e.g. during the development of a smart contract. If you have done any significant smart contract development on Tezos, it is very likely that you will have written, by hand, some or all of the code that Factori will generate for you in one second. It is even likely that you have rewritten it several times over, and taken significant time to debug your code when your smart contract inevitably changed and the data structures evolved. At least, at Functori, we found ourselves writing and debugging such tedious, boilerplate code over and over until we wrote this tool.

These interfaces can (currently) be generated in four distinct languages: Typescript, Python, C# and OCaml. There are specific subsections for each target programming language, listed below.

When you import a contract into your factori project, the SDK will be found in a subfolder of src.

List of supported languages:

Note that each interface involves some amount of static typing (to the level permitted by each language). The next section goes into why that is a good idea.

Why static interfaces?

Why do we generate static code in Factori? What are the advantages over a dynamic interface?

First, note that although the interface generated by Factori is static, it can be re-generated instantly, either because Factori has been updated or because the contract we are working on is evolving (we are developing it, or it has been "updated" on-chain in one of the limited ways this is even possible). It is static in the sense that we don't discover the interface at (contract-interacting, not entrypoint-inferring) runtime.

By contrast, a dynamic interface would typically generate an object, at runtime, containing various methods to interact with the contract (this is the case, for instance, of Pytezos and Taquito).

Typically, when we interact with a smart contract, we want to define, in advance, a storage and some parameters of entrypoint calls. With a dynamic interface and in a dynamic language, we could do this by creating values belonging to the type generated by the framework for the storage and parameters. As a consequence, this type must already exist before runtime.

  1. First: not all languages are dynamic, and this would not work in e.g. OCaml. If the type of the storage is a record, then we can't possibly write OCaml code which will accept this record before it has been defined.
  2. Second objection: even if we are working in a dynamic language, we are not certain that, when the contract or the framework changes, these dynamically-generated types are not going to change as well. We will discover this when trying to run our code interacting with the blockchain. Or maybe we won't discover it immediately because duck typing will coerce the previous type into the new one in unpredictable ways. To protect against this, a natural response is to use static typing.

With a static interface, we can access whatever amount of static typing our language provides. This is maximal in a language like OCaml, but Typescript has decent static typing, as do Python (3) and C#. Even if their static typing sometimes is an afterthought (on Javascript and Python), it is enough to catch some bugs and increase confidence in our SDK.

With a dynamic interface, we need either to trust the interface not to change, or to add static type annotations to our types ourselves. And of course, these static type annotations will need to change every time the contract changes, that is to say: some of what Factori does automatically will have to be done by hand.

All this being said, we make extensive use of the existing dynamic tools mentioned above: what we do is add a layer of static typing to protect the programmer against uncaught interface changes. Our work would not be possible without theirs, so shout out to Taquito, Netezos, and Pytezos.

Typescript

In order to use the Typescript Interface generation feature of Factori, you will need to have npm installed on your machine.

The Typescript SDK can be found in the src/typescript_sdk subfolder of your Factori project directory.

It is generated by activating the --typescript option of the factori import kt1 or factori import michelson commands, as seen below:

factori import kt1 <dir> KT1...XXX --network mainnet --name my_contract --typescript

<dir> is the working directory (it may be relative, such as .), and KT1...XXX is the address of the contract.

will create the directory <dir>/src/typescript_sdk where the following files and directories will appear:

  • src:
    • functolib.ts: A library of types and functions used by all imported interfaces
    • my_contract_code.json: the Micheline code of your contract (useful for deployment)
    • my_contract_interface.ts: the Typescript interface to your contract, described below.
  • public (empty folder for now)
  • package.json (javascript config file)
  • tsconfig.json (typescript config file)

The command also creates a Makefile with useful commands such as:

make ts-deps

which will install all needed typescript dependencies, and

make ts

which will compile your interface, but also any scenario which you may find yourself writing inside <dir>/src/typescript_sdk/. For instance, if you create a <dir>/src/typescript_sdk/scenario.ts, you can run:

make ts
node <dir>/src/dist/scenario.js

Description of the interface file

The interface file (my_contract_interface.ts using the convention from above) consists of both

  1. Types
  2. Functions

The types describe the contract's storage as well as all the input types of the contract's entrypoints (and any intermediate types which may need to be defined in the process). The functions are (mainly) of three kinds:

  1. A deploy function deploy_my_contract;
  2. Calling functions for each entrypoint, of the form call_<entrypoint_name>.
  3. Utility functions for manipulating types, so that in principle, you never have to use Micheline or Michelson directly:
    • Encoding functions from a type to Micheline (<type name>_encode);
    • Decoding functions from Micheline to a type (<type name>_decode);
    • Random generation of elements of the type (<type name>_generator);
    • (technically not a function, but rather a constant): the type itself expressed in Micheline; this is useful e.g. for querying big maps (<type name>_micheline).

Note about Taquito

Factori's Typescript generation uses the awesome Taquito library. Please note that although there are many similarities between the kind of storage that Taquito accepts for a given contract and that generated by Factori, they are not the same and it is not advised to mix them. Taquito generates a dynamic interface (you generally can't look at it until you have retrieved a storage and looked at it), while Factori generates a static interface (you know its exact structure in advance).

Python

In order to use the Python Interface generation feature of Factori, you will need to have pytezos installed on your machine.

The Python SDK can be found in the src/python_sdk subfolder of your Factori project directory.

It is generated by activating the --python option of the factori import kt1 or factori import michelson commands, as seen below:

factori import kt1 <dir> KT1...XXX --network mainnet --name my_contract --python

<dir> is the working directory (it may be relative, such as .), and KT1...XXX is the address of the contract.

will create the directory <dir>/src/python_sdk where the following files and directories will appear:

  • blockchain.py: A library for blockchain specific operations;
  • factori_types.py: A library of types and functions used by all imported interfaces;
  • my_contract_code.json: the Micheline code of your contract (useful for deployment);
  • my_contract_python_interface.py: the Python interface to your contract, described below.

The command also creates a Makefile with useful commands such as:

make python-static-check

which will typecheck all code (generated and custom added) using mypy and/or pyright (you can comment whichever line you don't want).

make python-compile

which will run python on all files of the interface, but also on any scenario which you may find yourself writing inside <dir>/src/python_sdk/. If you created a <dir>/src/python_sdk/scenario.py, you can also directly run:

python <dir>/python_sdk/scenario.py

Description of the interface file

The interface file (my_contract_python_interface.py using the convention from above) consists of both

  1. Types
  2. Functions

The types describe the contract's storage as well as all the input types of the contract's entrypoints (and any intermediate types which may need to be defined in the process). The functions are (mainly) of three kinds:

  1. A deploy function deploy_my_contract;
  2. Calling functions for each entrypoint, of the form call_<entrypoint_name>.
  3. Utility functions for manipulating types, so that in principle, you never have to use Micheline or Michelson directly:
    • Encoding functions from a type to Micheline (<type name>_encode);
    • Decoding functions from Micheline to a type (<type name>_decode);
    • Random generation of elements of the type (<type name>_generator).

Note about Pytezos

Factori's Python generation uses the awesome Pytezos library. Please note that although there are many similarities between the kind of storage that Pytezos accepts for a given contract and that generated by Factori, they are not the same and it is not advised to mix them. Pytezos generates a dynamic interface (you generally can't look at it until you have retrieved a storage and looked at it), while Factori generates a static interface (you know its exact structure in advance).

C#

In order to use the C# Interface generation feature of Factori, you will need to have Netezos and dotnet installed on your machine. For resources, look at:

  • Dotnet
  • https://netezos.dev/
  • https://github.com/baking-bad/netezos.

The C# SDK can be found in the src/csharp_sdk subfolder of your Factori project directory.

It is generated by activating the --csharp option of the factori import kt1 or factori import michelson commands, as seen below:

factori import kt1 <dir> KT1...XXX --network mainnet --name my_contract --csharp

<dir> is the working directory (it may be relative, such as .), and KT1...XXX is the address of the contract.

will create the directory <dir>/src/csharp_sdk where the following files and directories will appear:

  • blockchain.cs: A library for blockchain specific operations;
  • factori_types.cs: A library of types and functions used by all imported interfaces;
  • my_contract_code.json: the Micheline code of your contract (useful for deployment)
  • my_contract_csharp_interface.cs: the C# interface to your contract, described below.

The command also creates a Makefile with useful commands such as:

make csharp-init

which will initiate a C# project.

make format-csharp

will format the generated code.

make csharp-build

will build the code.

In order to run Program.cs, if you write e.g. a scenario in it, run

dotnet run --project src/csharp_sdk/

Description of the interface file

The interface file (my_contract_csharp_interface.cs using the convention from above) consists of both

  1. Types
  2. Functions

The types describe the contract's storage as well as all the input types of the contract's entrypoints (and any intermediate types which may need to be defined in the process). The functions are (mainly) of three kinds:

  1. A deploy function deploy_my_contract;
  2. Calling functions for each entrypoint, of the form call_<entrypoint_name>.
  3. Utility functions for manipulating types, so that in principle, you never have to use Micheline or Michelson directly:
    • Encoding functions from a type to Micheline (<type name>_encode);
    • Decoding functions from Micheline to a type (<type name>_decode);
    • Random generation of elements of the type (<type name>_generator).

OCaml

In order to use the OCaml Interface generation feature of Factori, you will need to have opam installed on your machine.

The OCaml SDK can be found in the src/ocaml_sdk subfolder of your Factori project directory.

It is generated by activating the --ocaml option of the factori import kt1 or factori import michelson commands, as seen below:

factori import kt1 <dir> KT1...XXX --network mainnet --name my_contract --ocaml

<dir> is the working directory (it may be relative, such as .), and KT1...XXX is the address of the contract.

will create the directories <dir>/src/ocaml_sdk, <dir>/src/ocaml_scenarios and <dir>/src/libraries. In src, the following files and directories will appear:

  • the src/libraries folder:
    • blockchain.ml: A library for blockchain specific operations;
    • factori_types.ml: A library of types and functions used by all imported interfaces;
    • factory_abstract_types.ml: A library for advanced scenarios;
    • utils.ml: Various utilities.
  • the src/ocaml_sdk folder:
    • my_contract_code.json: the Micheline code of your contract (useful for deployment)
    • my_contract_ocaml_interface.ml: the OCaml interface to your contract, described below.
    • my_contract_abstract_ocaml_interface.ml: the OCaml abstract interface to your contract, only useful for abstract scenarios.
  • the src/ocaml_scenarios folder
    • scenario.ml: an empty scenario file, ready with a dune file with all needed dependencies.

The command also creates a Makefile with useful commands such as:

make format-ocaml

which will format your project.

make format-ocaml

will format the generated code.

make ocaml

will build the code.

make run_scenario_ocaml

will run your scenario.

Description of the interface file

The interface file (my_contract_csharp_interface.ml using the convention from above) consists of both

  1. Types
  2. Functions

The types describe the contract's storage as well as all the input types of the contract's entrypoints (and any intermediate types which may need to be defined in the process). The functions are (mainly) of three kinds:

  1. A deploy function deploy_my_contract;
  2. Calling functions for each entrypoint, of the form call_<entrypoint_name>.
  3. Utility functions for manipulating types, so that in principle, you never have to use Micheline or Michelson directly:
    • Encoding functions from a type to Micheline (<type name>_encode);
    • Decoding functions from Micheline to a type (<type name>_decode);
    • Random generation of elements of the type (<type name>_generator);
    • (technically not a function, but rather a constant): the type itself expressed in Micheline; this is useful e.g. for querying big maps (<type name>_micheline).

Crawler configuration generation

This part of the documentation is still in the works. However, you can refer to the Quickstart section.

Web App generation

This part of the documentation is still in the works. However, you can refer to the Quickstart section.

Scenarios

Factori allows you to write scenarios for your smart contracts.

Scenarios

On this topic, see also our blog article which contains examples of OCaml scenarios and another one with elementary OCaml and Typescript scenarios.

Focus on the high-level

You can use the SDK generated by Factori to write scenarios for your smart contracts. This is useful at all stages of a smart contract's life: prototyping, testing, Dapp development, and deployment.

When you are developing a smart contract, you are bound to make many design changes, each of which might slightly change the interface of your contract. Instead of manually modifying storage and entrypoint parameter encodings each time you add or remove a field, you can just regenerate the interface (it's instantaneous, because Factori is really fast). Because our interfaces are statically typed, if your scenario is no more up to date with respect to the contract, instead of cryptic error from a Tezos node, you will get localized, easily understandable and fixable errors. For instance, if you just added the field admin (of type address to your storage, and your scenario deploys an initial storage without the field admin, you will get an error saying precisely this. In contrast, if you were deploying by hand, you might get an error from the node telling you that it expects a

Pair int (Pair address (Pair (Pair int string) (big_map address int)))

and got a

Pair int (Pair (Pair int string (big_map address int)))

instead...

Speed of iteration

It should be easy and fast to write and execute a scenario in Factori, on any Tezos blockchain (from Flextesa to ghostnet to mainnet). Using Flextesa, you can run it as fast as one block per second, which makes it easy to try out quick changes and adjust.

Defaults

Each contract imported from a blockchain comes with a its downloaded storage from the blockchain, so that you can quickly deploy it using the same storage with sensible defaults. Typically, you might deploy a contract with the same initial storage and change the admin to an address you control.

Advanced scenarios

For advanced uses, Factori comes with a Domain Specific Language (DSL) for scenarios. These uses are typically:

  • needing to compile scenarios down to several languages (e.g., OCaml and Typescript);
  • needing to execute several scenarios in parallel.

To learn more about this DSL, check out our extensive blog article on the topic.

Linting

Factori offers some linting and analysis capabilities on Michelson contracts. This feature is in a beta state:

Linting

Factori offers some linting capabilities on Michelson contracts. This feature is in a beta state.

Linting

The Factori lint instruction goal is to detect some syntactic problems in the Michelson file that we thought were worth keeping a trace of.

Smart contract structure

The structure of a Michelson smart contract is not predetermined, it has mandatory sections (like the parameter, storage, and code sections) but they can appear in a different order, it depends on the compilation. The order that Factori lint choose is this one: parameter -> storage -> code -> views. If a smart contract doesn't follow this order, the linter will print a warning telling what section is at the wrong place and what was expected.

Annotations

The annotations in the Michelson files are important, they define the entrypoint in the parameter and also the field of the storage.

Missing ones

The lint command shows when an annotation in the storage/parameter is missing. It will be easier for Factori to generate an interface with a lot of annotations.

Forbidden ones

Factori also holds a set of forbidden annotations to warn the user. For example, if you have a field of the storage named transfer_tokens, Factori will print a warning because it's a reserved word from Michelson. This set of forbidden annotations can grow or shrink depending on the evolution of the tool.

Incoherent ones

Michelson allows multiple uses of the same annotation. It can become confusing if the annotation %token_id defines a natural but at the same time another annotation %token_id defines a string. Factori detects these weird cases and warns the user.

Michelson Analysis

Factori offers some analysis capabilities on Michelson contracts. This feature is in a beta state.

Analysis

The factori analysis currently handles the following:

  • Unused storage variables detection: It can happen that a field in the storage of a contract is unused, which leads to unnecessary costs;
  • Use of set, list, map in storage: This might be dangerous as these data structures are unbounded and will be fully deserialized every time the contract is called. If they become too big, deserialization gas limits might be hit, preventing any future call to the smart contract.
  • ABS detection: The ABS instruction can break a contract's logic if it is (mis)used to convert integers to natural numbers without checking if the integer is negative. IS_NAT should be preferred to handle the failing case explicitly.
  • Reentrancy detection: To prevent reentrancy, the analysis returns a warning if it detects potential reentrancy with the TRANSFER_TOKENS Michelson instruction.
  • Permission analysis: Factori detects the conditions which lead to a failure, and then prints them for each entrypoint.

Quick deploy (re-originate)

Factori lets you quickly re-originate a contract you have imported from the blockchain.

The syntax is

factori deploy OPTIONS CONTRACT_NAME

where

  • CONTRACT_NAME is the name under which you have imported your contract. If you don't remember it, you can look up the file contracts.json in you Factori project folder, it will appear in the contract_name field as in: json "contracts": [ { "contract_name": "test", "original_format": "kT1", "original_kt1": [ "KT1Ty5iiGhgBKxUgAbNbSPU58dx6J3ULVRdk", "mainnet" ], "import_date": "13/4/2023 11:50:6", "options": [ "ocaml", "csharp" ] where the name of the contract is test.
  • OPTIONS can be
    • network is the network you wish to deploy to. Typical choices will be ghostnet (where alice from flextesa is an account which is always replenished and from which you can originate for free) or flextesa if you are deploying in a sandbox).
    • --ocaml means that an OCaml interface will be used to deploy the contract. For this to work, you will need to have generated the OCaml SDK for your contract;
    • --typescript means that a Typescript interface will be used to deploy the contract. For this to work, you will need to have generated the Typescript SDK for your contract.
    • --storage is the initial storage you want to use, it can be either random or blockchain:
      • random will use a randomly generated storage
      • blockchain will use the initial blockchain storage from the contract at the time you imported it. Please note that this will not, however, include the content of big_maps. Other languages will be added in the deploy command in the future.

When run, this command will generate a (OCaml or Typescript) scenario which deploys your contract to the desired network. Upon successful deployment, the scenario will be destroyed. If the deployment fails, the scenario will remain so that you can debug what went wrong or submit an issue.

Commands

You can print a list of commands by running

factori --help

You will get

FACTORI(1)                      Factori Manual                      FACTORI(1)

NAME
       factori - Manage and interact with Tezos smart contracts in multiple
       programming languages

SYNOPSIS
       factori [COMMAND] …

COMMANDS
       analyze kt1 [OPTION]… [KT1]
           Lint a KT1 using its Michelson compiled form.

       analyze michelson [--force] [--quiet] [--verbose] [OPTION]…
       [MICHELSON_FILE]
           Lint a contract using its Michelson compiled form. It can be in
           either a Json or normal Michelson format.

       deploy clean [--force] [--quiet] [--verbose] [OPTION]…
           cleanup leftover scenario of the `factori deploy` command

       deploy [OPTION]… [CONTRACT_NAME]
           Quickly deploy one of your imported contracts.

       empty project [OPTION]… [DIRECTORY]
           Create an empty Factori project. In most cases you don't need to do
           this, you can just start importing your contracts.

       help [--man-format=FMT] [OPTION]… [TOPIC]
           display help about factori and factori commands

       import kt1 [OPTION]… [DIRECTORY] [KT1]
           Import an on-chain contract using its KT1. You may specify from
           which network it should be pulled.

       import michelson [OPTION]… [DIRECTORY] [MICHELSON_FILE]
           Import a contract using its Michelson compiled form. It can be in
           either a Json or normal Michelson format.

       lint kt1 [OPTION]… [KT1]
           Lint a KT1 using its Michelson compiled form.

       lint michelson [--force] [--quiet] [--verbose] [OPTION]…
       [MICHELSON_FILE]
           Lint a contract using its Michelson compiled form. It can be in
           either a Json or normal Michelson format.

       remove contract [OPTION]… [DIRECTORY]
           Remove an existing contract from the project. If the --purge option
           is activated, it will remove the file, otherwise it will only
           remove it logically from the build infrastructure.

       rename variables [--force] [--quiet] [--verbose] [OPTION]…
       [CONTRACT_NAME]
           Rename some or all anonymous types (type0,type1,etc...) possibly
           generated by the previous build of the interface. This will be
           effective for all programming language interfaces (OCaml,
           Typescript, future languages)

       sandbox start [OPTION]…
           Start a Flextesa sandbox. You will need docker installed as well as
           proper permissions (see for instance
           https://docs.docker.com/engine/install/linux-postinstall/)

       sandbox stop [--force] [--quiet] [--verbose] [OPTION]…
           Stop the Flextesa sandbox started with Factori.

COMMON OPTIONS
       --help[=FMT] (default=auto)
           Show this help in format FMT. The value FMT must be one of auto,
           pager, groff or plain. With auto, the format is pager or plain
           whenever the TERM env var is dumb or undefined.

       --version
           Show version information.

EXIT STATUS
       factori exits with the following status:

       0   on success.

       123 on indiscriminate errors reported on standard error.

       124 on command line parsing errors.

       125 on unexpected internal errors (bugs).

Import KT1

Import an on-chain contract using its KT1.

factori import kt1 [OPTION]… [DIRECTORY] [KT1]
  • --crawlori

    • If activated, a crawlori plugin that can crawl the smart contract(s) will be generated (needs ocaml option).
  • --csharp

    • If activated, a C# interface for the smart contract(s) will be generated.
  • --db-name=db-name

    • This is the name for the psql database (default is project directory basename).
  • --dipdup

    • If activated, necessary files to use didup will be generated.
  • -f, --force

    • If set to true, Factori will overwrite the input folder for commands that write to files. Defaults to false.
  • --field_prefixes=field_prefixes

    • Determines whether record fields are prefixed by the entrypoint name to disambiguate when several entrypoints have the same parameter names.
  • --library

    • Determines whether the OCaml code is generated only as a library.
  • --name=CONTRACT_NAME

    • Provide the contract name for which you would like to perform the operation.
  • --network=network

    • Specify on which network (mainnet,ithacanet,etc...) you would like to perform the operation. Any prefix of more than three letters "mai", "main", "gho" will work.
  • --ocaml

    • If activated, an OCaml interface for the smart contract(s) will be generated.
  • --project_name=PROJECT_NAME

    • Provide a project name. This will be used e.g. for the opam file name.
  • --python

    • If activated, a Python interface for the smart contract(s) will be generated.
  • -q, --quiet

    • Set verbosity level to 0.
  • --typescript

    • If activated, a Typescript interface for the smart contract(s) will be generated.

Import Michelson

Import a contract using its Michelson compiled form. It can be in either a Json or normal Michelson format. The options are similar to import kt1:

factori import michelson [OPTION]… [DIRECTORY] [MICHELSON_FILE]
  • --crawlori

    • If activated, a crawlori plugin that can crawl the smart contract(s) will be generated (needs ocaml option).
  • --csharp

    • If activated, a C# interface for the smart contract(s) will be generated.
  • --db-name=db-name

    • This is the name for the psql database (default is project directory basename).
  • -f, --force

    • If set to true, Factori will overwrite the input folder for commands that write to files. Defaults to false.
  • --field_prefixes=field_prefixes

    • Determines whether record fields are prefixed by the entrypoint name to disambiguate when several entrypoints have the same parameter names.
  • --library

    • Determines whether the OCaml code is generated only as a library.
  • --name=CONTRACT_NAME

    • Provide the contract name for which you would like to perform the operation.
  • --ocaml

    • If activated, an OCaml interface for the smart contract(s) will be generated.
  • --project_name=PROJECT_NAME

    • Provide a project name. This will be used e.g. for the opam file name.
  • --python

    • If activated, a Python interface for the smart contract(s) will be generated.
  • -q, --quiet

    • Set verbosity level to 0.
  • --typescript

    • If activated, a Typescript interface for the smart contract(s) will be generated.
  • -v, --verbose

    • Increase verbosity level.
  • --web

    • Determines whether the Web page of the smart contract is generated.

Deploy and Deploy-clean

Deploy

This command enables you to quickly deploy one of your imported contracts. If you want to do a controlled, refined deploy, you should probably not use this command and write a scenario (in one of our 4 supported languages).

  • -f, --force

    • If set to true, Factori will overwrite the input folder for commands that write to files. Defaults to false.
  • --network=network

    • Specify on which network (mainnet,ithacanet,etc...) you would like to perform the operation.
  • --ocaml

    • The contract will be deployed using an OCaml scenario (only use this if you have imported an OCaml interface)
  • -q, --quiet

    • Set verbosity level to 0
  • --storage=storage

    • Storage type used for the factori deploy command.
  • --typescript

    • If activated, a Typescript interface for the smart contract(s) will be generated (only use this if you have imported a Typescript interface; in doubt between OCaml and Typescript, prefer Typescript which is simpler to set up).
  • -v, --verbose

    • Increase verbosity level.

Deploy-clean

The factori deploy-clean command will clean up the files generated by the deploy command for the purpose of originating your contract. It can be useful if these files are interfering with your development.

Empty project

Importing a contract into an empty folder will create a new project. However, in some cases, you may want to first create an empty project and e.g. install all dependencies to gain time before you actually start working with contracts. In this case, the command factori empty project, with similar options as factori import kt1 or factori import michelson will be useful.

  • --crawlori
    • If activated, a crawlori plugin that can crawl the smart contract(s) will be generated (needs ocaml option).
  • --csharp
    • If activated, a C# interface for the smart contract(s) will be generated.
  • --db-name=db-name
    • This is the name for the psql database (default is project directory basename).
  • -f, --force
    • If set to true, Factori will overwrite the input folder for commands that write to files. Defaults to false.
  • --library
    • Determines whether the OCaml code is generated only as a library.
  • --ocaml
    • If activated, an OCaml interface for the smart contract(s) will be generated.
  • --python
    • If activated, a Python interface for the smart contract(s) will be generated.
  • -q, --quiet
    • Set verbosity level to 0.
  • --typescript
    • If activated, a Typescript interface for the smart contract(s) will be generated.
  • -v, --verbose
    • Increase verbosity level.
  • --web
    • Determines whether the Web page of the smart contract is generated.

Sandbox

Factori provides a wrapper to quickly launch (and stop) a flextesa sandbox from Docker. You will need to have Docker installed.

  • sandbox start [OPTION]…

    • Start a Flextesa sandbox. You will need docker installed as well as proper permissions (see for instance https://docs.docker.com/engine/install/linux-postinstall/)
  • sandbox stop

    • Stop the Flextesa sandbox started with Factori.

Extra

In all likelihood, you won't be using Factori in isolation. In this section we describe how to use the Flextesa local network and the Explorus blockchain interface in conjunction with Factori.

Local network with Flextesa

You can start a Flextesa sandbox using Factori. This is very useful because you can make it as fast as one block per second; moreover, you have full access to an alice account with a large amount of funds (every generated SDK knows how to use this account). Run

factori sandbox start

to start it, and

factori sandbox stop

to stop it.

There is a --block-interval option which will set the number of seconds between blocks (default is 1).

You can monitor your Flextesa sandbox visually using Explorus.

Explorus Interface

Explorus is a full consensus explorer and light block explorer by Functori. It is very useful to monitor what is happening on any Tezos blockchain, and in particular a sandbox blockchain for which you have no out of the box explorer. Simply open https://explorus.functori.com/, go to the dented wheel on the top right corner, and enter the URL of your sandbox node. If you are using a default Flextesa setup, this will be http://localhost:20000.