Skip to content

Code & Deploy an home-made ICO
(Ethereum ~ Solidity ~ Node ~ Truffle ~ Webpack ~ Metamask ~ Infura ~ Heroku)
COMPLETE guide

This article intends to provide a full-blown guide on how to code and deploy a single-page Ethereum-powered ICO (Initial Coin Offering) web application. This is how the final result looks like.

We will use Ubuntu operating system and go through all the steps, from blank page to deployment onto the Ethereum Ropsten network.

I see many guides around describe only bits and pieces of the Ethereum development process. I felt something that described all the steps in detail was missing. Also, this guide is very suitable for those like me who have quite out-dated hardware and want to use a “light” architecture.

1. Get yourself ready

We will begin with installing on our machine all the software needed for the deployment.

Install git, node and npm

Firstly, we will need to install git (a version control system), nodejs (the run-time environment for executing JavaScript code server-side) and npm, the node package manager.

Open the terminal (CTRL + T) and type the below commands in sequence.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install -y build-essential
sudo apt-get install -y git
curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
sudo apt-get install -y nodejs

(Optional) Run the below to verify that the installation was successful

git --version # should return the git version
node -v       # should return the node version
npm -v        # should return the npm version

 

Install Truffle

We will then install Truffle, one of the most popular development frameworks for Ethereum. Note that the flag -g makes the installation global (which is quite important).

sudo npm install -g truffle

Again the command

truffle -v     #should return the Truffle version

should confirm that everything went well (as of the time of writing, the latest Truffle version 4.0.4 is highly recommended)

Install Metamask

Assuming that Google Chrome (recommended for this project) is installed on your system, we will now proceed to download and install Metamask Chrome extension.

Metamask is a bridge that allows you run Ethereum dApps right in your browser, without running a full Ethereum node. Essentially, instead than connecting directly to (and downloading the full) blockchain, the Metamask bridge connects for you to the blockchain as a “light” interface.

It is extremely simple to create an Ethereum wallet in Metamask, but for further reference you may want to watch this video.

During the new wallet creation procedure, you will be provided with a 12 word mnemonic. Make sure you write these down somewhere safe as they will be: a) your only way to recover the wallet, b) needed during the deployment process.

Register to Infura

Another step needed is to register to Infura.

After leaving your details to Infura, please take note of the API key. Infura is a service that provides secure, reliable, and scalable access to the Ethereum blockchain. Infura registration is required because Metamask cannot deploy smart contracts directly, but think about the two services as closely related.

Register and Install Heroku

Finally, we will start getting ready for deployment by creating an account on Heroku. Heroku is a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud. It is free to register and please take note of the username and password.

After the registration, please download the Heroku client running the commands below.

sudo add-apt-repository "deb https://cli-assets.heroku.com/branches/stable/apt ./"
curl -L https://cli-assets.heroku.com/apt/release.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install heroku

 

2. Prepare the environment

We will start by unleashing all the power of Truffle. The guys at Truffle have provided us with a few nice templates. We will use the “Truffle box” based on Webpack.

Create a new folder to contain the project and cd into it:

mkdir myfirstdapp
cd myfirstdapp

and run

truffle unbox webpack

Please give a few seconds for the box to unpack, and then get yourself familiar with the directory tree structure. There is another package that is not provided “out of the box” (no pun intended) and that needs to be installed running the below:

npm install truffle-hdwallet-provider --save

At this stage feel free to delete all the unnecessary boilerplate files from the truffle box, as per below:

rm /contracts/ConvertLib.sol
rm /contracts/Metacoin.sol
rm /migrations/2_deploy_contracts.js
rm /test/metacoin.js
rm /test/TestMetacoin.sol

 

3. Begin coding

To launch an ICO you would generally need:

  1. two Ethereum contracts (a Token and a Fundraise contract)
  2. a front/back-end infrastructure for your app and your users to interact with the contract (an html, a css and a javascript files)

In our example we are adding a third Ethereum contract, a Fund that will collect the proceeds of the Fundraise. This is just for demonstrative purpose.

The Smart Contracts

Below is the solidity code of the (very basic) Token. Copy/paste this code and save it in a file Token.sol in the /contracts folder.

It would be ideal if you tried to understand what this code does. Please refer to the Solidity documentation, the Ethereum token creation page and the ERC20 token standard if you are unsure.

Token.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
pragma solidity ^0.4.18;


contract Token {
    // Storage Variables
    string public name;
    string public symbol;
    uint256 public decimals;
    uint256 public totalSupply;

    mapping (address => uint256) public balances;

    function Token (string _name, string _symbol, uint256 _decimals, uint256 _initialSupply) public {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        totalSupply = _initialSupply * 10 ** decimals;
        balances[msg.sender] = totalSupply;
    }

    // Fallback function
    // Do not accept ethers
    function() public {
        // This will throw an exception - in effect no one can purchase the coin
        assert(true == false);
    }

    // Getters
    function totalSupply() public constant returns (uint) {
        return totalSupply;
    }

    function name() public constant returns (string) {
        return name;
    }

    function symbol() public constant returns (string) {
        return symbol;
    }

    function decimals() public constant returns (uint256) {
        return decimals;
    }

    // Real Utility functions
    function balanceOf(address _owner) public constant returns (uint256) {
        return balances[_owner];
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        // Check basic conditions
        require(balances[msg.sender] >= _value);
        require(_to != 0x0);
        require(_value > 0);

        // Another check for assertion
        uint previousBalances = balances[msg.sender] + balances[_to];

        // Executes the transfer
        balances[msg.sender] -= _value;
        balances[_to] += _value;

        // Assert that the total of before is equal to the total of now
        assert(balances[msg.sender] + balances[_to] == previousBalances);

        return true;
    }

}

Below is the code for the Fundraise, to be named Fundraise.sol and saved again in the /contracts folder.

Fundraise.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
pragma solidity ^0.4.18;


interface token {
    function transfer(address to, uint256 value);
}


contract Fundraise {

    /*

    Storage Variables - remain in memory

    */


    // Keep track of the fundraising timeline
    bool public fundraiseWasStarted = false;
    bool public fundraiseIsOpen = false;
    uint256 public deadline;

    // Keep track of the fundraising target in Ether
    uint256 public minimumTargetInWei;
    bool public targetReached = false;

    // Keep track of the amount raised so far
    uint256 public amountRaisedSoFarInWei = 0;

    // Token used as a reward for the fundraise
    // and conversion price
    token public tokenToBeUsedAsReward;
    uint256 public priceOfTokenInWei;

    // Fundraise beneficiary and owner
    address public fundraiseBeneficiary;
    address public fundraiseOwner;

    // Keep track of the Ether balances of contributors
    mapping(address => uint256) public balances;

    // Constructor
    function Fundraise() public {
        fundraiseOwner = msg.sender;
    }

    // Modifiers
    // Function that can be actioned only by the owner
    modifier onlyOwner {
        require(msg.sender == fundraiseOwner);
        _;
    }

    // Function that can be actioned only when the fundRaise
    // was started and closed
    modifier fundraiseWasStartedAndDoesNotMatterTheRest() {
        require(fundraiseWasStarted == true);
        _;
    }

    modifier fundraiseWasStartedAndNowClosed() {
        require(fundraiseIsOpen == false && fundraiseWasStarted == true);
        _;
    }

    modifier fundraiseWasStartedAndStillOpen() {
        require(fundraiseIsOpen == true && fundraiseWasStarted == true);
        _;
    }

    modifier fundraiseWasNotYetStarted() {
        require(fundraiseIsOpen == false && fundraiseWasStarted == false);
        _;
    }

    // Opens the fundraise, only if it was not so before
    function openFundraise(
            uint256 _timeOpenInMinutes,
            uint256 _minimumTargetInEthers,
            address _tokenToBeUsedAsReward,
            uint256 _priceOfTokenInEther,
            address _fundraiseBeneficiary
        ) public onlyOwner fundraiseWasNotYetStarted {
        fundraiseIsOpen = true;
        fundraiseWasStarted = true;
        deadline = now + (_timeOpenInMinutes * 1 minutes);
        minimumTargetInWei = _minimumTargetInEthers * 1 ether;
        tokenToBeUsedAsReward = token(_tokenToBeUsedAsReward);
        priceOfTokenInWei = _priceOfTokenInEther * 1 ether;
        fundraiseBeneficiary = _fundraiseBeneficiary;
    }

    // At any time can check the status of fundraise
    function checkStatusOfFundraise() public fundraiseWasStartedAndDoesNotMatterTheRest {
        if (now >= deadline) {
            fundraiseIsOpen = false;
        }

        if (amountRaisedSoFarInWei >= minimumTargetInWei) {
            targetReached = true;
        }
    }

    function contributeToFundraise() public payable fundraiseWasStartedAndStillOpen {
        uint amount = msg.value;
        balances[msg.sender] += amount;
        amountRaisedSoFarInWei += amount;
        tokenToBeUsedAsReward.transfer(msg.sender, amount / priceOfTokenInWei);
    }

    function withdraw() public payable fundraiseWasStartedAndNowClosed {
        if (targetReached == false) {
            uint amount = balances[msg.sender];
            balances[msg.sender] = 0;
            if (msg.sender.send(amount)) {
                tokenToBeUsedAsReward.transfer(fundraiseOwner, amount / priceOfTokenInWei);    
            }
        } else if (targetReached == true && fundraiseBeneficiary == msg.sender) {
            if (fundraiseBeneficiary.send(amountRaisedSoFarInWei)){

            } else {
                targetReached = false;
            }
        }
    }
}

For the Fund the code is much simpler (almost an empty template). Copy/paste and save in Fund.sol in the /contracts folder. Note: do not run this on the live Ethereum network as I have not implemented a way to withdraw the Ether from the Fund (on purpose).

Fund.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
pragma solidity ^0.4.18;


interface fundraise {
    function withdraw();
}


contract Fund {

    uint256 public balanceInWei;
    string public fundName;
    address public fundOwner;

    function Fund(string _name) public {
        fundName = _name;
        fundOwner = msg.sender;
    }

    // Modifiers
    // Function that can be actioned only by the owner
    modifier onlyOwner {
        require(msg.sender == fundOwner);
        _;
    }

    function () public payable {

    }

    function fundName() public constant returns (string) {
        return fundName;
    }

    function callWithdraw(address _currentFundraise) onlyOwner payable {
        fundraise(_currentFundraise).withdraw();
    }

    function getBalance() public returns (uint256) {
        balanceInWei = this.balance;
        return balanceInWei;
    }


}

 

The Front-end

The front-end – which is modelled upon the front-end framework Bootstrap – will be defined in the index.html file (placed in the app folder) as per below. Please feel free to do some customisation.

Note that it is explicitly designed to handle connection to the Ropsten testnet. Any attempt to deploy on mainnet would require some further customisation.

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<!DOCTYPE html>
<html>
<head>
  <title>Fundraise DApp</title>
  <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous">
  <script src="app.js"></script>
</head>
<body>

    <!-- Image and text -->
    <nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
    <div class="container">
      <a class="navbar-brand" href="http://www.massimilianoterzi.it">
        <img src="./images/massi.png" width="30" height="30" class="d-inline-block align-top" alt="">    
        Massi's Fundraise Dapp
      </a>
    </div>
    </nav>


    <main role="main" class="container">

    <div class="alert alert-info" role="alert">You are connected on <span id="identifiedNet"></span> network</div>

    <div class="card-deck">
        <div class="card" style="width: 20rem">
                <div class="card-header">
                    Your Current Account
                </div>
          <div class="card-body">
            <p class="card-text">Ether Balance: <span id="youretherbalance0"></span></p>
            <p class="card-text">Your Token Balance: <span id="yourtokenbalance0"></span> (<span id="tokenpercentage0"></span>%)</p>
          </div>
          <div class="card-footer">
            <small class="text-muted">Your Address: <a target="_blank" title="Check on Etherscan" id="youraddress0link" href=""><span id="youraddress0"></span></a></small>
          </div>
        </div>
    </div>
    <br>

    <!-- Example single danger button -->
    <div class="btn-group">
      <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
        Open Fundraise
      </button>
      <div class="dropdown-menu" style="width: 25rem" aria-labelledby="dLabel">
        <div class="px-4 py-3">
          <div class="form-group form-inline">
            <label for="OpenFundraiseFormMinimumTarget">Minimum Target: </label>
            <input type="text" class="form-control" id="OpenFundraiseFormMinimumTarget" placeholder="Min. Target (in Ether)" required>
          </div>
          <div class="form-group form-inline">
            <label for="OpenFundraiseFormDeadline">Time Open: </label>
            <input type="text" class="form-control" id="OpenFundraiseFormDeadline" placeholder="Time Open (In Minutes)" required>
          </div>
          <div class="form-group form-inline">
            <label for="OpenFundraiseFormPriceOfToken">Price of Token (in Ether): </label>
            <input type="text" class="form-control" id="OpenFundraiseFormPriceOfToken" placeholder="Price of Token (In Ether)" required>
          </div>
          <div class="form-group form-inline">
            <label for="OpenFundraiseFormBeneficiary">Beneficiary Address: </label>
            <input type="text" class="form-control" id="OpenFundraiseFormBeneficiary" placeholder="Beneficiary Address" required>
          </div>
          <div class="form-group form-inline">
            <label for="OpenFundraiseFormTokenReward">Token Address: </label>
            <input type="text" class="form-control" id="OpenFundraiseFormTokenReward" placeholder="Token Address" required>
          </div>
          <button class="btn btn-primary" onclick="App.launchFundraise()">Launch</button>
        </div>
      </div>
    </div>

    <!-- Example single danger button -->
    <div class="btn-group">
      <button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
        Contribute
      </button>
      <div class="dropdown-menu" style="width: 25rem" aria-labelledby="dLabel">
        <div class="px-4 py-3">
          <div class="form-group form-inline">
            <label for="ContributeDropdownFormAmount">Amount</label>
            <input type="text" class="form-control" id="ContributeDropdownFormAmount" placeholder="Amount (in Ether)">
          </div>
          <button class="btn btn-primary" onclick="App.contributeToFundraiseApp()">Contribute</button>
        </div>
      </div>
    </div>

    <!-- Example single danger button -->
    <div class="btn-group">
      <button class="btn btn-info" onclick="App.checkFundraiseStatus()">
        Check Fundraise Status
      </button>
    </div>

    <!-- Example single danger button -->
    <div class="btn-group">
      <button class="btn btn-warning" onclick="App.withdrawAmountsApp()">
        Withdraw
      </button>
    </div>

    <br>
    <br>
        <div class="card-deck">
          <div class="card">
                <div class="card-header">
                    Token
                </div>
            <div class="card-body">
              <p class="card-text">Token Name: <span id="tokenname"></span></p>
              <p class="card-text">Token Symbol: <span id="tokensymbol"></span></p>
              <p class="card-text">Token Decimals: <span id="tokendecimals"></span></p>
              <p class="card-text">Token Supply: <span id="tokentotalsupply"></span></p>
            </div>
            <div class="card-footer">
              <small class="text-muted">Token Address: <a target="_blank" title="Check on Etherscan" id="tokenaddresslink" href=""><span id="tokenaddress"></span></a></small>
            </div>
          </div>
          <div class="card">
                <div class="card-header">
                    Fundraise
                </div>
            <div class="card-body">
              <p class="card-text">Started: <span id="hasfundraisestarted"></span></p>
              <p class="card-text">Token Balance: <span id="fundraisetokenbalance"></span></p>
              <p class="card-text">Ether Balance: <span id="fundraiseetherbalance"></span></p>
              <p class="card-text">Target: <span id="fundraisetarget"></span></p>            
              <p class="card-text">Deadline: <span id="fundraisedeadline"></span></p>
              <p class="card-text">Open: <span id="isfundraiseopen"></span></p>
              <p class="card-text">Target Reached: <span id="isfundraisetargetreached"></span></p>
            </div>
            <div class="card-footer">
              <small class="text-muted">FundRaise Address: <a target="_blank" title="Check on Etherscan" id="fundraiseaddresslink" href=""><span id="fundraiseaddress"></span></a></small>
            </div>
          </div>
          <div class="card">
                <div class="card-header">
                    Fund
                </div>
            <div class="card-body">
              <p class="card-text">Fund Name: <span id="fundname"></span></p>
              <p class="card-text">Fund EtherBalance: <span id="fundetherbalance"></span></p>
            </div>
            <div class="card-footer">
              <small class="text-muted">Fund Address: <a target="_blank" title="Check on Etherscan" id="fundaddresslink" href=""><span id="fundaddress"></span></a></small>
            </div>
          </div>
        </div>
    <br>
    </main>

  <footer class="footer fixed-bottom">
    <div class="container">
      <span class="text-muted">© 2018, Massimiliano Terzi</span>
    </div>
  </footer>

</body>
<script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4" crossorigin="anonymous"></script>

</html>

 

The Back-end

The back-end is provided below. It is a javascript file that has a number of tasks:

  1. It connects to the Ethereum web3 provider (Metamask in our case)
  2. It connects the deployed contracts to the graphical front-end interface
  3. It refreshes the UI every time there is a change or an interaction with the blockchain

The file needs to be (re)-placed in the app/javascript directory.

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
// Import the page's CSS. Webpack will know what to do with it.
import "../stylesheets/app.css";

// Import libraries we need.
import { default as Web3} from 'web3';
import { default as contract } from 'truffle-contract'

// Import our contract artifacts and turn them into usable abstractions.
import massitoken_artifacts from '../../build/contracts/Token.json'
import fund_artifacts from '../../build/contracts/Fund.json'
import fundraise_artifacts from '../../build/contracts/Fundraise.json'

// MetaCoin is our usable abstraction, which we'll use through the code below.
var MassiToken = contract(massitoken_artifacts);
var Fund = contract(fund_artifacts);
var Fundraise = contract(fundraise_artifacts);

// The following code is simple to show off interacting with your contracts.
// As your needs grow you will likely need to change its form and structure.
// For application bootstrapping, check out window.addEventListener below.
var accounts;
var account;
var account1;


window.App = {
  start: function() {
    var self = this;

    // Bootstrap the MetaCoin abstraction for Use.
    MassiToken.setProvider(web3.currentProvider);
    Fund.setProvider(web3.currentProvider);
    Fundraise.setProvider(web3.currentProvider);

    var identifiedNetwork;
   
    web3.version.getNetwork((err, netId) => {
      switch (netId) {
        case "1":
          identifiedNetwork = "Mainnet";
          console.log('This is mainnet');
          break;
        case "2":
          identifiedNetwork = "Morden";
          console.log('This is the deprecated Morden test network.');
          break;
        case "3":
          identifiedNetwork = "Ropsten";
          console.log('This is the ropsten test network.');
          break;
        case "4":
          identifiedNetwork = "Rinkeby";
          console.log('This is the Rinkeby test network.');
          break;
        case "42":
          identifiedNetwork = "Kovan";
          console.log('This is the Kovan test network.');
          break;
        default:
          identifiedNetwork = "Unknown";
          console.log('This is an unknown network.');
      }
    document.getElementById("identifiedNet").innerHTML = identifiedNetwork;
    });

    // Get the initial account balance so it can be displayed.
    web3.eth.getAccounts(function(err, accs) {
      if (err != null) {
        alert("There was an error fetching your accounts.");
        return;
      }

      if (accs.length == 0) {
        alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly.");
        return;
      }

      accounts = accs;
      account = accounts[0];
      // account1 = accounts[1];

      var accountInterval = setInterval(function() {
        if (web3.eth.accounts[0] !== account) {
          account = web3.eth.accounts[0];
          self.updateInterface();
        }
      }, 5000);

      document.getElementById("youraddress0").innerHTML = account;
      $("#youraddress0link").attr("href", "https://ropsten.etherscan.io/address/" + account);
      // document.getElementById("youraddress1").innerHTML = account1;

      web3.eth.getBalance(account, function(err,result){
        if(err){
          console.log(err);
          console.log("Error getting balance");
        } else{
          document.getElementById("youretherbalance0").innerHTML = web3.fromWei(result,'ether');  
        }
      });

      // web3.eth.getBalance(account1, function(err,result){
      //   if(err){
      //     console.log(err);
      //     console.log("Error getting balance");
      //   } else{
      //     document.getElementById("youretherbalance1").innerHTML = web3.fromWei(result,'ether');  
      //   }
       
      // });
     
      self.updateInterface();

    });
  },

  updateInterface: function() {
    var self = this;

    self.getTokenAddress();
    self.getFundraiseAddress();
    self.getFundAddress();
    self.getTokenName();
    self.getFundName();
    self.getTokenTotalSupply();
    self.getTokenSymbol();
    self.getTokenDecimals();
    self.getPercentageOwned();
    self.getFundEtherBalance();
    self.getFundraiseEtherBalance();
    self.getFundraiseStatus();
    self.getFundraiseTarget();
    self.getFundraiseisStarted();
    self.getFundraiseisTargetReached();
    self.getFundraiseDeadline();
    self.refreshTokenBalanceAccounts();
    self.refreshTokenBalanceFundraise();
 
  },

  refreshTokenBalanceAccounts: function() {
    var self = this;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      return token.balanceOf.call(account, {from: account});
    }).then(function(value) {
      var balance_element = document.getElementById("yourtokenbalance0");
      balance_element.innerHTML = value.valueOf();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting balance; see log 1.");
    });

    // MassiToken.deployed().then(function(instance) {
    //   token = instance;
    //   return token.balanceOf.call(account1, {from: account1});
    // }).then(function(value) {
    //   var balance_element1 = document.getElementById("yourtokenbalance1");
    //   balance_element1.innerHTML = value.valueOf();
    // }).catch(function(e) {
    //   console.log(e);
    //   console.log("Error getting balance; see log 2.");
    // });    

  },

  refreshTokenBalanceFundraise: function() {
    var self = this;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      var fundraiseaddress = document.getElementById("fundraiseaddress").innerHTML;
      return token.balanceOf(fundraiseaddress);
    }).then(function(tokenbalancefundraise) {
      var balance_element2 = document.getElementById("fundraisetokenbalance");
      balance_element2.innerHTML = tokenbalancefundraise.valueOf();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting balance; see log 3.");
    });

  },  

  getTokenAddress: function() {
    var self = this;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      return token.address;
    }).then(function(address) {
      var address_element = document.getElementById("tokenaddress");
      address_element.innerHTML = address.valueOf();
      var contribution_element = document.getElementById("OpenFundraiseFormTokenReward");
      contribution_element.value = address.valueOf();
      $("#tokenaddresslink").attr("href", "https://ropsten.etherscan.io/address/" + address.valueOf());
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting address; see log.");
    });
  },

  getFundraiseAddress: function() {
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function(instance) {
      fundraise = instance;
      return fundraise.address;
    }).then(function(address) {
      var fundraise_address_element = document.getElementById("fundraiseaddress");
      fundraise_address_element.innerHTML = address.valueOf();
      $("#fundraiseaddresslink").attr("href", "https://ropsten.etherscan.io/address/" + address.valueOf());
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting address; see log.");
    });
  },

  getFundraiseTarget: function() {
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function(instance) {
      fundraise = instance;
      return fundraise.minimumTargetInWei.call();
    }).then(function(mintarget) {
      var fundraise_target_element = document.getElementById("fundraisetarget");
      fundraise_target_element.innerHTML = web3.fromWei(mintarget.valueOf());
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting fundraising target; see log.");
    });
  },

  getFundraiseStatus: function() {
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function(instance) {
      fundraise = instance;
      return fundraise.fundraiseIsOpen.call();
    }).then(function(fundraisestatus) {
      var status_element = document.getElementById('isfundraiseopen');
      status_element.innerHTML = fundraisestatus.toString();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting fundraise status; see log.");
    });
  },

  getFundraiseisStarted: function() {
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function(instance) {
      fundraise = instance;
      return fundraise.fundraiseWasStarted.call();
    }).then(function(started) {
      var started_element = document.getElementById('hasfundraisestarted');
      started_element.innerHTML = started.toString();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting whether fundraise was started; see log.");
    });
  },

  getFundraiseisTargetReached: function() {
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function(instance) {
      fundraise = instance;
      return fundraise.targetReached.call();
    }).then(function(targetreach) {
      var target_reach_element = document.getElementById('isfundraisetargetreached');
      target_reach_element.innerHTML = targetreach.toString();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting whether fundraise target was reached; see log.");
    });
  },

  getFundAddress: function() {
    var self = this;

    var fund;
    Fund.deployed().then(function(instance) {
      fund = instance;
      return fund.address;
    }).then(function(address) {
      var fund_address_element = document.getElementById("fundaddress");
      fund_address_element.innerHTML = address.valueOf();
      var contribution_element = document.getElementById("OpenFundraiseFormBeneficiary");
      contribution_element.value = address.valueOf();
      $("#fundaddresslink").attr("href", "https://ropsten.etherscan.io/address/" + address.valueOf());
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting address; see log.");
    });
  },

  getFundName: function() {
    var self = this;

    var fund;
    Fund.deployed().then(function(instance) {
      fund = instance;
      return fund.fundName.call();
    }).then(function(ilnome) {
      var fund_name_element = document.getElementById("fundname");
      fund_name_element.innerHTML = ilnome.valueOf();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting fund name; see log.");
    });
  },

  getFundraiseDeadline: function() {
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function(instance) {
      fundraise = instance;
      return fundraise.deadline.call();
    }).then(function(deadline) {
      var newDate = new Date();
      newDate.setTime(parseInt(deadline)*1000);
      var dateString = newDate.toUTCString();
      var deadline_element = document.getElementById('fundraisedeadline');
      deadline_element.innerHTML = dateString;
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting fundraise deadline; see log.");
    });
  },

  getTokenTotalSupply: function() {
    var self = this;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      return token.totalSupply.call();
    }).then(function(supply) {
      var totalsupply_element = document.getElementById("tokentotalsupply");
      totalsupply_element.innerHTML = supply.valueOf();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting token total supply; see log.");
    });
  },

  getTokenName: function() {
    var self = this;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      return token.name.call();
    }).then(function(name) {
      var name_element = document.getElementById("tokenname");
      name_element.innerHTML = name.valueOf();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting token name; see log.");
    });
  },

  getTokenSymbol: function() {
    var self = this;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      return token.symbol.call();
    }).then(function(symbol) {
      var symbol_element = document.getElementById("tokensymbol");
      symbol_element.innerHTML = symbol.valueOf();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting token symbol; see log.");
    });
  },

  getTokenDecimals: function() {
    var self = this;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      return token.decimals.call();
    }).then(function(decimals) {
      var decimals_element = document.getElementById("tokendecimals");
      decimals_element.innerHTML = decimals.valueOf();
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting token decimals; see log.");
    });
  },

  getFundEtherBalance: function() {
    var self = this;

    var fund;
    Fund.deployed().then(function(instance) {
      fund = instance;
      web3.eth.getBalance(fund.address, function(error,result){
        if(error){
          console.log(error);
        } else {
          var balance_element = document.getElementById("fundetherbalance");
          var balanceinEther = web3.fromWei(result, 'ether');
          balance_element.innerHTML = balanceinEther.valueOf();
        }
      });
    });
  },

  getFundraiseEtherBalance: function() {
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function(instance) {
      fundraise = instance;
      web3.eth.getBalance(fundraise.address, function(error,result){
        if(error){
          console.log(error);
        } else {
          var balance_element = document.getElementById("fundraiseetherbalance");
          var balanceinEther = web3.fromWei(result, 'ether');
          balance_element.innerHTML = balanceinEther.valueOf();
        }
      });
    });
  },

  getPercentageOwned: function() {
    var self = this;

    let balanceofcurrentaddress0;
    let balanceofcurrentaddress1;

    var token;
    MassiToken.deployed().then(function(instance) {
      token = instance;
      return token.balanceOf.call(account, {from: account});
    }).then(function(value) {
      balanceofcurrentaddress0 = value.valueOf();
      return token.totalSupply.call();
    }).then(function(supply) {
      var totalSupply = supply.valueOf();
      var percentage = balanceofcurrentaddress0/totalSupply * 100;
      document.getElementById("tokenpercentage0").innerHTML = percentage;
    }).catch(function(e) {
      console.log(e);
      console.log("Error getting percentage; see log.");
    });
  },

  sendCoin: function() {
    var self = this;

    var amount = parseInt(document.getElementById("amount").value);
    var receiver = document.getElementById("receiver").value;

    console.log("Initiating transaction... (please wait)");

    var meta;
    MetaCoin.deployed().then(function(instance) {
      meta = instance;
      return meta.sendCoin(receiver, amount, {from: account});
    }).then(function() {
      console.log("Transaction complete!");
      self.refreshBalance();
    }).catch(function(e) {
      console.log(e);
      console.log("Error sending coin; see log.");
    });
  },

  launchFundraise: function(){
    var self = this;

    var target = parseInt(document.getElementById('OpenFundraiseFormMinimumTarget').value);
    var timeopeninminutes = parseInt(document.getElementById('OpenFundraiseFormDeadline').value);
    var priceoftokeninether = parseInt(document.getElementById('OpenFundraiseFormPriceOfToken').value);
    var tokentobeusedasreward = document.getElementById('OpenFundraiseFormTokenReward').value;
    var fundraisebeneficiary = document.getElementById('OpenFundraiseFormBeneficiary').value;

    var fundraise;
    Fundraise.deployed().then(function (instance) {
      fundraise = instance;
      return fundraise.openFundraise(timeopeninminutes,
        target, tokentobeusedasreward, priceoftokeninether,
        fundraisebeneficiary, {from: account, gas: 1000000});
    }).then(function() {
      console.log("Fundraise Launch successful");
      self.getFundraiseStatus();
      self.getFundraiseisStarted();
      self.getFundraiseTarget();
      self.getFundraiseDeadline();
      self.getFundraiseisTargetReached();
    }).catch(function(e) {
      console.log(e);
      console.log("Error launching Fundraise; see log.");
    });
  },

  contributeToFundraiseApp: function(){
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function (instance) {
      fundraise = instance;
      return web3.toWei(parseInt(document.getElementById('ContributeDropdownFormAmount').value),'ether');
    }).then(function(contrib){
      return fundraise.contributeToFundraise({from: web3.eth.accounts[0], value: contrib, gas: 1000000});
    }).then(function() {
      console.log("Contribution Successful");
      self.refreshTokenBalanceAccounts();
      self.refreshTokenBalanceFundraise();
      self.getFundraiseEtherBalance();
    }).catch(function(e) {
      console.log(e);
      console.log("Error contributing to Fundraise; see log.");
    });
  },

  withdrawAmountsApp: function(){
    var self = this;

    var fundraiseaddress = document.getElementById("fundraiseaddress").innerHTML;

    var fund;
    Fund.deployed().then(function (instance) {
      fund = instance;
      return fund.callWithdraw(fundraiseaddress, {from: account});
    }).then(function() {
      console.log("Success in withdrawing amount");
      self.getFundEtherBalance();
      self.getFundraiseEtherBalance();
      self.refreshTokenBalanceFundraise();
    }).catch(function(e) {
      console.log(e);
      console.log("Error withdrawing amounts; see log.");
    });
  },

  checkFundraiseStatus: function(){
    var self = this;

    var fundraise;
    Fundraise.deployed().then(function (instance) {
      fundraise = instance;
      return fundraise.checkStatusOfFundraise({from: account});
    }).then(function() {
      console.log("Success in checking fundraise status");
      self.getFundraiseisTargetReached();
      self.getFundraiseStatus();
    }).catch(function(e) {
      console.log(e);
      console.log("Error checking Fundraise status; see log.");
    });
  }

};

window.addEventListener('load', function() {
  // Checking if Web3 has been injected by the browser (Mist/MetaMask)
  if (typeof web3 !== 'undefined') {
    console.warn("Using web3 detected from external source. If you find that your accounts don't appear or you have 0 MetaCoin, ensure you've configured that source properly. If using MetaMask, see the following link. Feel free to delete this warning. :) http://truffleframework.com/tutorials/truffle-and-metamask")
    // Use Mist/MetaMask's provider
    window.web3 = new Web3(web3.currentProvider);
  } else {
    console.warn("No web3 detected. Falling back to http://127.0.0.1:9545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask");
    // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
    window.web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:9545"));
  }

  App.start();
});

 

4. Wrap everything up

The Blockchain Deployment

In Truffle, the process of deploying the smart contracts to the Ethereum blockchain is called migration. The word confused me at first, but consider it part of the jargon. To migrate your contracts onto the blockchain, you need to adjust the configurations of two files.

The first has to be named 2_deploy_contracts.js and contains the parameters that will be attributed to the creation of the contracts. Copy/paste the code below.

This file:

  • Deploys a token called “MassiToken”, with symbol “MAS”, zero decimals and an initial supply of 1,000 MAS
  • Deploys a fund called “MassiFund”
  • Deploys a fundraise. The parameters of the fundraise will be specified by the user in the front-end

2_deploy_contracts.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var Token = artifacts.require("./Token.sol");
var Fund = artifacts.require("./Fund.sol");
var Fundraise = artifacts.require("./Fundraise.sol")

module.exports = function(deployer) {
  deployer.deploy(Token, "MassiToken", "MAS", 0, 1000);

  var baseAccount;
  var accounts;

  web3.eth.getAccounts(function(err, accs) {
      if (err != null) {
        alert("There was an error fetching your accounts.");
        return;
      }

      if (accs.length == 0) {
        alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly.");
        return;
      }

      accounts = accs;
      baseAccount = accounts[0];
      deployer.deploy(Fund, "MassiFund");
      deployer.deploy(Fundraise, baseAccount);
  });

};

The second file is the configuration for Truffle and instructs where to deploy. The custom file contains only the “development” configuration. Therefore, in order to work on the Ropsten network, it has to be customised as per below.

Do not forget to edit the mnemonic (your 12 words Metamask seed) and the infura_api_key (you should have gotten this when you registered on Infura.

truffle.js

var HDWalletProvider = require("truffle-hdwallet-provider");
var mnemonic = "YOUR 12 WORD MNEMONIC PASTE IT HERE";

var infura_api_key = "YOUR INFURA KEY GOES HERE"

// Allows us to use ES6 in our migrations and tests.
require('babel-register')

module.exports = {
  networks: {
    development: {
      host: '127.0.0.1',
      port: 7545,
      network_id: '*' // Match any network id
    },
    ropsten: {
      provider: new HDWalletProvider(mnemonic, "https://ropsten.infura.io/" + infura_api_key),
      network_id: 3,
      gas: 4600000
    }      
  }
}

At this point, all the ingredients are ready to compile the contracts and migrate them on the blockchain.

Run the code below in the terminal window.

truffle compile                           # compiles the contracts
truffle migrate --reset --network ropsten # migrates the contracts on Ropsten

Nonetheless, if you still feel unsure and would like to try to see if everything works on a local network, please run the following

truffle develop                            # creates a 'dummy' blockchain on your machine
> compile                               # compiles the contracts
> migrate --reset --network development # migrates the contracts on the local development blockchain

 

The Web Deployment

The web deployment on Heroku is not a process without potential issues and a number of files will have to customised (i.e. they are obtained with the Truffle webpack but have to be edited to make them work in deployment).

Please edit the webpack.config.js file as per below (in particular, please notice the devServer string which is not part of the original configuration.

webpack.config.js

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  entry: './app/javascripts/app.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'app.js'
  },
  plugins: [
    // Copy our app's index.html to the build folder.
    new CopyWebpackPlugin([
      { from: './app/index.html', to: "index.html" },
      { from: './app/images', to: "images" }
    ])
  ],
  module: {
    rules: [
      {
       test: /\.css$/,
       use: [ 'style-loader', 'css-loader' ]
      }
    ],
    loaders: [
      { test: /\.json$/, use: 'json-loader' },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015'],
          plugins: ['transform-runtime']
        }
      }
    ]
  },
  devServer: {
    port: process.env.PORT || 8080,
    host: '0.0.0.0',
    disableHostCheck: true
  }
}

The package.json file also needs to be tweaked: Heroku does not install devDependencies out of the box. The easiest shortcut (adopted below) is to move all the devDependencies into Dependencies so they are installed automatically by Heroku.

package.json

{
  "name": "truffle-init-webpack",
  "version": "0.0.2",
  "description": "Frontend example using truffle v3",
  "scripts": {
    "lint": "eslint ./",
    "build": "webpack",
    "dev": "webpack-dev-server"
  },
  "author": "Douglas von Kohorn",
  "license": "MIT",
  "dependencies": {
    "babel-cli": "^6.22.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^6.1.2",
    "babel-loader": "^6.2.10",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-preset-env": "^1.1.8",
    "babel-preset-es2015": "^6.22.0",
    "babel-register": "^6.22.0",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.26.1",
    "eslint": "^3.14.0",
    "eslint-config-standard": "^6.0.0",
    "eslint-plugin-babel": "^4.0.0",
    "eslint-plugin-mocha": "^4.8.0",
    "eslint-plugin-promise": "^3.0.0",
    "eslint-plugin-standard": "^2.0.0",
    "html-webpack-plugin": "^2.28.0",
    "json-loader": "^0.5.4",
    "style-loader": "^0.13.1",
    "truffle-contract": "^1.1.11",
    "truffle-hdwallet-provider": "0.0.3",
    "web3": "^0.20.0",
    "webpack": "^2.2.1",
    "webpack-dev-server": "^2.3.0"
  }
}

One of the latest steps before being able to deploy to the web is to add a so-called Procfile in the app main directory. This file represents the first command that gets executed by Heroku when someone tries to access the website online. In our case, it’s a one-liner.

Procfile

web: npm run dev

 

Deploy the app to Heroku

First run

heroku login

and enter your credentials to log-in (username and password that you used for Heroku registration).

Then initialize a git repository and push the code onto Heroku. Run:

git init
git add .
git commit -m "Added a Procfile"
heroku create
git push heroku master

(Optional) You can add a .gitignore file in the app main directory. The gitignore does exactly what it’s called: all the files or directories specified in the gitignore will not be deployed onto Heroku.

5. Play with your newly create Dapp (Decentralized Application)

Once the Dapp is deployed on the Ropsten Ethereum network and the web, you can start playing with it.

  1. Launch a fundraise with a push of a button (need to set a target, the price for your token, and a deadline in minutes)
  2. Contribute some of your token to the fundraise so that it can be used as a reward for those who will invest Ether (usually you would not want to contribute >50% of your issued tokens, read why here
  3. Check the fundraise status
  4. Close the fundraise and have the fund withdraw the contributed Ether

Note that this code and example is purely intended as an illustrative example to learn the Ethereum deployment steps. It should not be used (in part or in whole) in a real ICO.

Feel free to write me if you have questions, spot some bugs or would just like to contribute to the article.

Would also be happy to take Ether donations to: 0x5456068c000073478969b98086bECB79159cED66

Published inProgrammingTechnology