ICO

Introduction#

This ICO process is inspired by the 2018 BCDiploma's ICO described in this document.

Contributors are whitlisted with their address to participate in the ICO. Two whitelists are available:

  • A silver whitelist, with a contribution limit of 10 XTZ maximum
  • A gold whitelist, without contribution limit

The minimum transaction is 0.1 XTZ. 100,000,000 tokens go on sale. The ICO takes place in 3 rounds, with a contribution limit for each, and gives rise to different bonuses:

  • Presale, limited to 1800 XTZ, grant 20% more tokens at the time of the contribution
  • Round 1, limited to the presale cap + 1800 XTZ, grant 10% more tokens during the contribution
  • Round 2 does not give rise to a bonus, limited to the 100 000 000 tokens put up for sale. The XTZ/TOKEN rate is set at 80.

If the cap is reached for each of these rounds, the round is automatically completed. Otherwise, the round can be completed manually by the owner of the smartcontract.

Token ownership is handled by a FA 1.2 fungible token smart contract. Initial tokens are owned by the ICO contract owner.

API#

Storage#

NameTypeDescription
owneraddressAddress of the contract and intital tokens owner.
tokenaddressAddress of the FA 1.2 fungible token.
min_contributiontezMinimum contribution
max_contribution_silvertezMaximum contribution for Silver contributors
max_token_to_sellnatNumber of tokens to sell.
exchange_rate_tez_toknatNumber of tokens to receive for 1 tez
presale_captezNumber of tezies raised at the end of presale phase
round1_captezNumber of tezies raised at the end of Round 1 phase
nb_tok_soldnatNumber of tokens sold.
nb_tez_raisedtezNumber of tezis raised.
contributorcollectionA contributor is defined by:
  • address
  • type Silver or Gold
  • Contribution in tezis
vstategstateContract state

Entrypoints#

NameParametersDescription
registera, tRegisters address a as contributor in whitelist type t.
startpresaleowner can start Presale phase.
startround1owner can start Round 1 phase.
startround2owner can start Round 2 phase.
finishphaseowner can finish ICO phase.
contributeA registered contributor tranfers tezies to this entrypoint and receive tokens in exchange.
collectraisedowner can collect contract balance.

Code#

ico.arl
archetype ico(owner : address, token : address)
variable min_contribution : tez = 0.1tz
variable max_contribution_silver : tez = 10tz
variable max_token_to_sell : nat = 100_000_000
variable exchange_rate_tez_tok : nat = 80 (* one tez is 80 tokens *)
variable presales_cap : tez = 1800tz
variable round1_cap : tez = 3600tz
variable nb_tok_sold : nat = 0
variable nb_tez_raised : tez = 0tz
enum whitelist =
| Silver
| Gold
asset contributor identified by id {
id : address;
typ : whitelist;
contribution : tez = 0tz;
}
enum gstate =
| Init initial
| PresaleRunning
| PresaleFinished
| Round1Running
| Round1Finished
| Round2Running
| Round2Finished
variable vstate : gstate = Init
function is_running () : bool {
return
match vstate with
| PresaleRunning | Round1Running | Round2Running -> true
| _ -> false
end
}
function get_rate () : rational {
var coeff : rational =
match vstate with
| PresaleRunning -> 1.2
| Round1Running -> 1.1
| _ -> 1
end;
return (coeff * exchange_rate_tez_tok)
}
function get_remaining_tez_to_raise () : tez {
return
match vstate with
| PresaleRunning | PresaleFinished -> presales_cap - nb_tez_raised
| Round1Running | Round1Finished -> round1_cap - nb_tez_raised
| _ -> (((max_token_to_sell - nb_tok_sold) / exchange_rate_tez_tok) * 1tz)
end
}
function transition_to_finished () : gstate {
return
match vstate with
| PresaleRunning -> PresaleFinished
| Round1Running -> Round1Finished
| Round1Finished -> Round2Running
| _ -> Round2Finished
end
}
entry register(a : address, t : whitelist) {
called by owner
require { r0 : vstate = Init }
effect { contributor.add({ id = a; typ = t }) }
}
entry startpresales() {
called by owner
require { r1 : vstate = Init }
effect { vstate := PresaleRunning }
}
entry startround1() {
called by owner
require { r2: vstate = PresaleFinished }
effect { vstate := Round1Running }
}
entry startround2() {
called by owner
require { r3: vstate = Round1Finished }
effect { vstate := Round2Running }
}
entry finishphase () {
called by owner
require { r4: is_running() }
effect { vstate := transition_to_finished() }
}
entry contribute () {
require {
c1 : contributor.contains(caller);
c2 : is_running ();
c3 : transferred >= min_contribution;
}
effect {
(* cap contribution to max_contrib if necessary *)
var contrib = transferred;
if contributor[caller].typ = Silver
and contributor[caller].contribution + contrib >= max_contribution_silver
then contrib := max_contribution_silver - contributor[caller].contribution;
(* cap contribution to round cap if necessary *)
var remaining_tez : tez = get_remaining_tez_to_raise ();
if remaining_tez <= contrib
then (
contrib := remaining_tez;
vstate := transition_to_finished ()
);
(* convert contribution to nb of bcd tokens *)
var nb_tokens : nat = get_rate() * contrib;
(* transfer tokens to contributor *)
transfer 0tz to token
call %transfer<address * address * nat>((owner, caller, nb_tokens));
(* update ico stats *)
nb_tok_sold += nb_tokens;
nb_tez_raised += contrib;
(* update caller's contribution *)
contributor[caller].contribution += contrib;
if contrib <= transferred
then transfer (transferred - contrib) to caller
}
}
entry collectraised () {
called by owner
require { r5: vstate = Round2Finished }
effect { transfer balance to owner }
}