2.10 Onward and Further

Let’s review what we’ve done through this tutorial:

Despite having done so much, this is really just a brief introduction to what is possible with Reach.

How difficult was all this? Let’s look at the final versions of our programs.

First, let’s look at the Reach program:

 1    'reach 0.1';
 2    
 3    const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
 4    const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
 5    
 6    const winner = (handA, handB) =>
 7          ((handA + (4 - handB)) % 3);
 8    
 9    assert(winner(ROCK, PAPER) == B_WINS);
10    assert(winner(PAPER, ROCK) == A_WINS);
11    assert(winner(ROCK, ROCK) == DRAW);
12    
13    forall(UInt, handA =>
14      forall(UInt, handB =>
15        assert(isOutcome(winner(handA, handB)))));
16    
17    forall(UInt, (hand) =>
18      assert(winner(hand, hand) == DRAW));
19    
20    const Player =
21          { ...hasRandom,
22            getHand: Fun([], UInt),
23            seeOutcome: Fun([UInt], Null),
24            informTimeout: Fun([], Null) };
25    const Alice =
26          { ...Player,
27            wager: UInt };
28    const Bob =
29          { ...Player,
30            acceptWager: Fun([UInt], Null) };
31    
32    const DEADLINE = 100;
33    export const main =
34      Reach.App(
35        {},
36        [Participant('Alice', Alice), Participant('Bob', Bob)],
37        (A, B) => {
38          const informTimeout = () => {
39            each([A, B], () => {
40              interact.informTimeout(); }); };
41    
42          A.only(() => {
43            const wager = declassify(interact.wager); });
44          A.publish(wager)
45            .pay(wager);
46          commit();
47    
48          B.only(() => {
49            interact.acceptWager(wager); });
50          B.pay(wager)
51            .timeout(DEADLINE, () => closeTo(A, informTimeout));
52    
53          var outcome = DRAW;
54          invariant(balance() == 2 * wager && isOutcome(outcome) );
55          while ( outcome == DRAW ) {
56            commit();
57    
58            A.only(() => {
59              const _handA = interact.getHand();
60              const [_commitA, _saltA] = makeCommitment(interact, _handA);
61              const commitA = declassify(_commitA); });
62            A.publish(commitA)
63              .timeout(DEADLINE, () => closeTo(B, informTimeout));
64            commit();
65    
66            unknowable(B, A(_handA, _saltA));
67            B.only(() => {
68              const handB = declassify(interact.getHand()); });
69            B.publish(handB)
70              .timeout(DEADLINE, () => closeTo(A, informTimeout));
71            commit();
72    
73            A.only(() => {
74              const [saltA, handA] = declassify([_saltA, _handA]); });
75            A.publish(saltA, handA)
76              .timeout(DEADLINE, () => closeTo(B, informTimeout));
77            checkCommitment(commitA, saltA, handA);
78    
79            outcome = winner(handA, handB);
80            continue; }
81    
82          assert(outcome == A_WINS || outcome == B_WINS);
83          transfer(2 * wager).to(outcome == A_WINS ? A : B);
84          commit();
85    
86          each([A, B], () => {
87            interact.seeOutcome(outcome); });
88          exit(); });

Next, the JavaScript command-line frontend:

 1    import { loadStdlib } from '@reach-sh/stdlib';
 2    import * as backend from './build/index.main.mjs';
 3    import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
 4    
 5    (async () => {
 6      const stdlib = await loadStdlib();
 7    
 8      const isAlice = await ask(
 9        `Are you Alice?`,
10        yesno
11      );
12      const who = isAlice ? 'Alice' : 'Bob';
13    
14      console.log(`Starting Rock, Paper, Scissors! as ${who}`);
15    
16      let acc = null;
17      const createAcc = await ask(
18        `Would you like to create an account? (only possible on devnet)`,
19        yesno
20      );
21      if (createAcc) {
22        acc = await stdlib.newTestAccount(stdlib.parseCurrency(1000));
23      } else {
24        const secret = await ask(
25          `What is your account secret?`,
26          (x => x)
27        );
28        acc = await stdlib.newAccountFromSecret(secret);
29      }
30    
31      let ctc = null;
32      const deployCtc = await ask(
33        `Do you want to deploy the contract? (y/n)`,
34        yesno
35      );
36      if (deployCtc) {
37        ctc = acc.deploy(backend);
38        const info = await ctc.getInfo();
39        console.log(`The contract is deployed as = ${JSON.stringify(info)}`);
40      } else {
41        const info = await ask(
42          `Please paste the contract information:`,
43          JSON.parse
44        );
45        ctc = acc.attach(backend, info);
46      }
47    
48      const fmt = (x) => stdlib.formatCurrency(x, 4);
49      const getBalance = async () => fmt(await stdlib.balanceOf(acc));
50    
51      const before = await getBalance();
52      console.log(`Your balance is ${before}`);
53    
54      const interact = { ...stdlib.hasRandom };
55    
56      interact.informTimeout = () => {
57        console.log(`There was a timeout.`);
58        process.exit(1);
59      };
60    
61      if (isAlice) {
62        const amt = await ask(
63          `How much do you want to wager?`,
64          stdlib.parseCurrency
65        );
66        interact.wager = amt;
67      } else {
68        interact.acceptWager = async (amt) => {
69          const accepted = await ask(
70            `Do you accept the wager of ${fmt(amt)}?`,
71            yesno
72          );
73          if (accepted) {
74            return;
75          } else {
76            process.exit(0);
77          }
78        };
79      }
80    
81      const HAND = ['Rock', 'Paper', 'Scissors'];
82      const HANDS = {
83        'Rock': 0, 'R': 0, 'r': 0,
84        'Paper': 1, 'P': 1, 'p': 1,
85        'Scissors': 2, 'S': 2, 's': 2,
86      };
87      interact.getHand = async () => {
88        const hand = await ask(`What hand will you play?`, (x) => {
89          const hand = HANDS[x];
90          if ( hand == null ) {
91            throw Error(`Not a valid hand ${hand}`);
92          }
93          return hand;
94        });
95        console.log(`You played ${HAND[hand]}`);
96        return hand;
97      };
98    
99      const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
100      interact.seeOutcome = async (outcome) => {
101        console.log(`The outcome is: ${OUTCOME[outcome]}`);
102      };
103    
104      const part = isAlice ? backend.Alice : backend.Bob;
105      await part(ctc, interact);
106    
107      const after = await getBalance();
108      console.log(`Your balance is now ${after}`);
109    
110      done();
111    })();

And finally, the Web frontend:

 1    import React from 'react';
 2    import AppViews from './views/AppViews';
 3    import DeployerViews from './views/DeployerViews';
 4    import AttacherViews from './views/AttacherViews';
 5    import {renderDOM, renderView} from './views/render';
 6    import './index.css';
 7    import * as backend from './build/index.main.mjs';
 8    import {loadStdlib} from '@reach-sh/stdlib';
 9    const reach = loadStdlib(process.env);
10    const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
11    const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
12    const {standardUnit} = reach;
13    const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};
14    
15    class App extends React.Component {
16      constructor(props) {
17        super(props);
18        this.state = {view: 'ConnectAccount', ...defaults};
19      }
20      async componentDidMount() {
21        const acc = await reach.getDefaultAccount();
22        const balAtomic = await reach.balanceOf(acc);
23        const bal = reach.formatCurrency(balAtomic, 4);
24        this.setState({acc, bal});
25        try {
26          const faucet = await reach.getFaucet();
27          this.setState({view: 'FundAccount', faucet});
28        } catch (e) {
29          this.setState({view: 'DeployerOrAttacher'});
30        }
31      }
32      async fundAccount(fundAmount) {
33        await reach.transfer(this.state.faucet, this.state.acc, reach.parseCurrency(fundAmount));
34        this.setState({view: 'DeployerOrAttacher'});
35      }
36      async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
37      selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
38      selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
39      render() { return renderView(this, AppViews); }
40    }
41    
42    class Player extends React.Component {
43      random() { return reach.hasRandom.random(); }
44      async getHand() { // Fun([], UInt)
45        const hand = await new Promise(resolveHandP => {
46          this.setState({view: 'GetHand', playable: true, resolveHandP});
47        });
48        this.setState({view: 'WaitingForResults', hand});
49        return handToInt[hand];
50      }
51      seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
52      informTimeout() { this.setState({view: 'Timeout'}); }
53      playHand(hand) { this.state.resolveHandP(hand); }
54    }
55    
56    class Deployer extends Player {
57      constructor(props) {
58        super(props);
59        this.state = {view: 'SetWager'};
60      }
61      setWager(wager) { this.setState({view: 'Deploy', wager}); }
62      async deploy() {
63        const ctc = this.props.acc.deploy(backend);
64        this.setState({view: 'Deploying', ctc});
65        this.wager = reach.parseCurrency(this.state.wager); // UInt
66        backend.Alice(ctc, this);
67        const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
68        this.setState({view: 'WaitingForAttacher', ctcInfoStr});
69      }
70      render() { return renderView(this, DeployerViews); }
71    }
72    
73    class Attacher extends Player {
74      constructor(props) {
75        super(props);
76        this.state = {view: 'Attach'};
77      }
78      attach(ctcInfoStr) {
79        const ctc = this.props.acc.attach(backend, JSON.parse(ctcInfoStr));
80        this.setState({view: 'Attaching'});
81        backend.Bob(ctc, this);
82      }
83      async acceptWager(wagerAtomic) { // Fun([UInt], Null)
84        const wager = reach.formatCurrency(wagerAtomic, 4);
85        return await new Promise(resolveAcceptedP => {
86          this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
87        });
88      }
89      termsAccepted() {
90        this.state.resolveAcceptedP();
91        this.setState({view: 'WaitingForTurn'});
92      }
93      render() { return renderView(this, AttacherViews); }
94    }
95    
96    renderDOM(<App />);

We wrote 88 lines of Reach and two different frontends. Our command-line version is 111 lines of JavaScript, or 199 lines together. While our Web version is 96 lines of JavaScript, or 184 lines together.

Behind the scenes, Reach generated 831 lines of Solidity (which you can look at here: tut-8/build/index.main.sol), 1621 lines of TEAL (which you can look at here: tut-8/build/index.main.mjs#L584), as well as 1889 lines of JavaScript (which you can look at here: tut-8/build/index.main.mjs). If we weren’t using Reach, then we’d have to write these 4346 lines ourselves and ensure that they are consistent and updated at every change to the application.

Now that you’ve seen an entire Reach application from beginning to end, it’s time for you to start working on your own applications!

No matter what you decide to read or work on next, we hope you’ll join us on the Discord community. Once you join, message @team, I just completed the tutorial! and we’ll give you the tutorial veteran role, so you can more easily help others work through it!

Thanks for spending your afternoon with us!