Skip to content

Latest commit

 

History

History
492 lines (386 loc) · 18 KB

README.md

File metadata and controls

492 lines (386 loc) · 18 KB

Content

LIGO library of reusable modules to implement FA2 contracts.

Table of Contents

Modular Contracts

The FA2 interface is designed to support a wide range of token kinds and implementations. The developer has to choose from multiple options when implementing a specific FA2 contract. Besides choosing between fungible and non-fungible tokens, a developer needs to decide whether new tokens can be minted and burned, how the contract administrators can be set, and what entry points should have admin access only.

This package provides reusable contract modules implemented in the CameLIGO language that can be composed into a single FA2 contract. The developer can use either the tzGen CLI tool or a programmatic API to generate the final contract code. A generated FA2 contract is composed of several orthogonal features. Each feature defines a set of possible options that can be selected independently. A combination of selected options for all supported features defines a specification describing resulting FA2 contract behavior. Available features and their options are described below.

Token Kind

This feature defines the kinds of tokens supported by the FA2 contract. Available options are listed below:

  • USE_NFT_TOKEN - contract implementation will support multiple non-fungible tokens (similar to Ethereum ERC-721).
  • USE_FUNGIBLE_TOKEN - contract implementation will support a single fungible token (similar to Ethereum ERC-20).
  • USE_MULTI_FUNGIBLE_TOKEN - contract implementation will support multiple fungible tokens (similar to Ethereum ERC-1155).

Minter Functionality

This feature defines optional support for token minting and burning. Multiple options from the list can be selected at the same time. If none of the options are selected, the resulting FA2 contract will not provide mint/burn functionality.

  • CAN_MINT - contract can mint new tokens.
  • CAN_BURN - contract can burn tokens.
  • CAN_FREEZE - contract can be frozen. Once an FA2 contract is frozen, no new tokens can be minted or burned. (However, existing tokens still can be transferred.) This option can be selected only if either CAN_MINT or CAN_BURN (or both) are selected.

Contract Admin

A contract can define some privileged entry points that can be accessed by the current contract admin address. There are several available options defining the admin feature implementation:

  • USE_NO_ADMIN - contract does not have an admin. Every entry point can be invoked by any address.
  • USE_SIMPLE_ADMIN - contract has a single admin.
  • USE_PAUSABLE_SIMPLE_ADMIN - contract has a single admin. The admin can pause and unpause the contract. (A paused contract cannot transfer its tokens.)
  • USE_MULTI_ADMIN - contract can have multiple admins. An admin can pause and unpause the contract.

Minter Admin

This feature defines access to mint and burn functionality defined by the minter feature.

  • USE_NULL_MINTER_ADMIN - contract does not have a minter admin. When neither CAN_MINT nor CAN_BURN feature is selected, anyone can mint or burn tokens. This is also the default option if neither mint nor burn feature is selected.
  • USE_ADMIN_AS_MINTER - contract admin can also mint and burn tokens.
  • USE_MULTI_MINTER_ADMIN - contract can have multiple minter admins that can mint and burn tokens. The minter admin list is separate from the contract admin(s).

Contract Specification Example

Token Kind (implementation): USE_NFT_TOKEN,
Minter: [CAN_MINT, CAN_FREEZE],
Admin: USE_PAUSABLE_SIMPLE_ADMIN
Minter Admin: USE_ADMIN_AS_MINTER

The resulting FA2 contract will support NFTs, be able to mint new tokens, and freeze the token collection after minting. The contract will have a simple (single) admin that can pause and unpause it. Only the admin will be able to mint tokens and freeze the NFT collection.

tzGen CLI Tool

fa2-contracts package includes tzGen CLI tool that generates CameLIGO contract code and TypeScript interface to initialize contract storage for the contract origination and to interact with the originated contract.

Initial Setup

First, you will need to add a development dependency on the @oxheadalpha/fa2-contracts package by running the following command:

$ yarn add -D @oxheadalpha/fa2-contracts

It is also possible to install the package globally.

Import LIGO Code

Import reusable LIGO modules code into your project directory:

$ yarn tzgen import-ligo [dir]

You may specify a project subdirectory name as the command argument. The default option is ./ligo.

Example:

$ yarn tzgen import-ligo
LIGO sources imported to ~/your_project/ligo

Initialize tzGen Environment

tzGen needs to know where your LIGO source code, TypeScript source code, and compiled Michelson contracts are located. The init command creates a tzGen environment configuration file tzgen.json and has the following options:

  • --ligo <ligo_dir> - LIGO source code directory (same as the directory used for import-ligo command). The default is ./ligo.
  • --compile-out <out_dir> - LIGO compilation output directory to put compiled Michelson files. The default is ./ligo/out.
  • --ts <ts_dir> - TypeScript source directory. Used to put generated TypeScript files. The default is ./src.

Example:

$ yarn tzgen init --compile-out ./dist
~/your_project/tzgen.json config file created

Generated tzgen.json file:

{
  "ligoDir": "./ligo",
  "compileOutDir": "./dist",
  "tsSourceDir": "./src"
}

Create FA2 Contract Specification

Before generating a contract code or TypeScript API, you need to create a contract specification by selecting a combination of features described in the modular contracts section. spec command accepts a name of the resulting specification file and the following required options:

  • --kind <kind> FA2 token kind. Available options are:
    • NFT non-fungible tokens
    • FT single fungible token
    • MFT multi-fungible-tokens
  • -admin <admin> type of the contract admin. Available options are:
    • NO_ADMIN contract does not have an admin.
    • SIMPLE contract has a simple admin.
    • PAUSABLE contract has a simple admin and can be paused.
    • MULTI contract has multiple admins and can be paused.
  • --minter [minter...] a list of the minting features. Available options are:
    • MINT contract can mint tokens.
    • BURN contract can burn tokens.
    • FREEZE mint/burn operations can be frozen.
  • --minter_admin <minter_admin> type of the minter admin implementation. Available options are:
    • NO_ADMIN contract does not have a minter admin role. Everyone can mint/burn tokens.
    • CONTRACT_ADMIN minter admin is the same as the contract admin.
    • MULTI contract can have multiple minter admins.

Example:

$ yarn tzgen spec my_contract.json --kind NFT --admin PAUSABLE --minter MINT FREEZE --minter_admin CONTRACT_ADMIN
~/my_project/my_contract.json spec file created

Generated my_contract.json file:

{
  "implementation": "USE_NFT_TOKEN",
  "admin": "USE_PAUSABLE_SIMPLE_ADMIN",
  "minter": [
    "CAN_MINT",
    "CAN_FREEZE"
  ],
  "minterAdmin": "USE_ADMIN_AS_MINTER"
}

Generate LIGO Code

Now you can use a contract specification file to generate a contract code and compile it. contract command takes two arguments: the name of the specification file and the name of the resulting CameLIGO file. The resulting file will be created in src subdirectory of the LIGO sources location (./ligo/src/ in our case).

$ yarn tzgen contract my_contract.json my_contract.mligo
contract source code file ~/my_project/ligo/src/my_contract.mligo is generated

Generate Michelson Code

The michelson command generates Michelson code from the contract CameLIGO source code (in other words, compiles the contract). The command takes two required arguments: contract source file name and output file name. It can also take an option --main, specifying the main entry point function. The generated contract source code will have the main entry point function, named asset_main. The default value for the --main option of the michelson command is the same, thus the option can be omitted. The resulting file will be created in the LIGO output directory from tzGen configuration (./dist in our case).

Example:

$ yarn tzgen michelson my_contract.mligo my_contract.tz
ligo version 0.68.0

compiled contract to ~/my_project/dist/my_contract.tz file

Generate TypeScript Code

The type-script command generates a TypeScript interface for the contract from the specification file. The command takes two arguments: the name of the specification file and the name of the resulting TypeScript file. The resulting file will be created in the TypeScript source code location (./src in our case).

Example:

$ yarn tzgen type-script my_contract.json my_contract.ts
contract interface source code file ~/my_project/src/my_contract.ts is generated

The resulting file will contain two functions: createStorage to create a storage object for the contract origination and createContractInterface to get a strongly typed interface to interact with the contract on the block chain. To use and compile a generated file, your package must include dependencies on @taquito/taquito and @oxheadalpha/fa2-interfaces packages.

Below is the generated TypeScript code for our example contract specification:

import { TezosToolkit } from '@taquito/taquito';
import * as fa2 from '@oxheadalpha/fa2-interfaces';
import { address, tezosApi } from '@oxheadalpha/fa2-interfaces';

export const createStorage = fa2.contractStorage
  .with(fa2.pausableSimpleAdminStorage)
  .with(fa2.nftStorage)
  .with(fa2.mintFreezeStorage)
  .build;

export const createContractInterface = async (
  toolkit: TezosToolkit,
  address: address
) =>
  (await tezosApi(toolkit).at(address))
    .withFa2()
    .withPausableSimpleAdmin()
    .asNft()
    .withMint()
    .withFreeze()
    ;

Those functions may be used for the contract origination:

const storage = createStorage({
    metadata: jsonMetadata,
    owner: ownerAddress
  });
  const code = fs.readFileSync('../dist/my_contract.tz', {
    encoding: 'utf8',
    flag: 'r'
  });
  const originationOp = await tz.contract.originate({ code, storage });
  const contract = await originationOp.contract();

and interaction with the originated one:

const fa2 = await createContractInterface(tz, contractAddress);
await runMethod(fa2.mintFreeze());

For more details about how to use and customize contract interface combinators please refer to @oxheadalpha/fa2-interfaces package documentation.

Programmatic API

Besides the tzGen CLI tool, it is also possible to provide the contract specification and generate the LIGO contract code using programmatic API. The combinator API lets you define the contract specification and then call generate() method to generate the contract code.

Implementation Combinators

First, you need to choose a token kind by selecting one of three Implementation combinators:

  • nft() implement NFT FA2 contract.
  • fungible() implement a single fungible token FA2 contract.
  • multiFungible() implement multiple fungible tokens FA2 contract.

Contract Admin Combinators

  • withNoAdmin() contract does not have an admin.
  • withSimpleAdmin() contract has a single admin.
  • withPausableSimpleAdmin() contract has a single admin that can pause and unpause the contract.
  • withMultiAdmin() contract has multiple admins that can pause and unpause the contract.

Token Minter Combinators

Minter combinators are optional and let you specify whether the FA2 contract will be able to mint and burn new tokens.

  • withNoMinter() contract cannot mint or burn tokens.
  • withMint() contract can mint new tokens.
  • withBurn() contract can burn tokens.
  • withFreeze() mint/burn functionality can be frozen.

Minter Admin Combinator

If a contract has mint/burn functionality, you have to select minter admin implementation as well.

  • withNoMinterAdmin() no minter admin. Anyone can mint/burn tokens.
  • withAdminAsMinter() contract admin is also a minter admin.
  • withMultiMinterAdmin() contract has multiple minter admins.

Generate Contract Code

Once you selected a contract specification using the combinator API you can invoke generate() method to generate contract code as in the example below:

const contractCode =
  Implement.nft()
    .withPausableSimpleAdmin()
    .withMint()
    .withFreeze()
    .withAdminAsMinter()
    .generate();

CameLigo Modules

  • fa2 FA2 interface and standard errors definition.
  • fa2/lib helpers, various LIGO modules used for the FA2 implementation.
  • token core implementation of the FA2 functionality and entry points for various token types.
  • minter implementation of mint and burn functionality. Each minter module corresponds to one of the token core implementation modules.
  • admin various implementations of the contract admin module. The admin may pause/unpause the contract and have access to other privileged contract entry points. Each admin implementation has a common LIGO module signature.
  • minter_admin various implementations of the contract minter admin (minter is an address that has rights to mint new tokens). Each minter admin implementation has a common LIGO module signature.

Common LIGO Admin Module Signature

module type AdminSig = sig

  type storage

  type entrypoints

  val fail_if_not_admin : storage -> unit

  val fail_if_not_admin_ext : storage * string -> unit

  val is_admin : storage -> bool

  val fail_if_paused : storage -> unit

  val main : entrypoints * storage -> (operation list) * storage

end
  • storage type of the admin storage (used as a part of the whole contract storage).
  • entrypoints type of the admin entry points
  • main implementation of the admin entry points (used by the contract main entry point).
  • is_admin, fail_if_not_admin, fail_if_not_admin_ext functions to guard privileged operations in the contract implementation.
  • is_paused function to guard a paused contract.

Common LIGO Minter Admin Module Signature

module type MinterAdminSig = sig

  type storage

  type entrypoints

  val is_minter : storage -> bool

  val main : entrypoints * storage -> (operation list) * storage

end
  • storage type of the minter admin storage (used as a part of the whole contract storage).
  • entrypoints type of the minter admin entry points.
  • main implementation of the minter admin entry points (used by the contract main entry point).
  • is_minter function to guard access to mint tokens operation(s).