Here’s a look at what we’ve done in this tutorial:
In 2.1, we learned how to install Reach by command on Windows/Linux/Mac systems, where there is no technical barrier for most developers. In 2.2, we learned that Reach can be set up in just a few lines of code and three key API calls. In 2.3, we saw how Reach lets developers focus on the business logic of a decentralized application, cutting out the minutiae of blockchain interaction and protocol design. In 2.4, we learned that Reach handles token and network transactions as easily as sharing data. In 2.5, we introduce you to the Reach automatic validation engine, which ensures that our programs are free of type defects and security vulnerabilities. In 2.6, we learned how Reach handles late participant responses and how to prevent funds from being locked up by contracts. In 2.7, we saw how Reach handles draw situations with loops and how the front end of Reach is flexible enough to adapt to changes at the back end. In 2.8, we learned how to separate Reach applications from the Reach test environment and distribute interactive applications on the real network. In 2.9, we learned how to deploy the Reach application as a decentralized Web application. Even with all the work that has been done, this is just a brief introduction to Reach’s capabilities. The final version completed in the nine sections above is quite simple, so let’s take a look at the final version of the program. First, let’s look at the Reach program: TU-8 /index.rsh
'reach 0.1';
const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
const winner = (handA, handB) = >
((handA + (4 - handB)) % 3);
assert(winner(ROCK, PAPER) == B_WINS);
assert(winner(PAPER, ROCK) == A_WINS);
assert(winner(ROCK, ROCK) == DRAW);
forall(UInt, handA= >
forall(UInt, handB= >
assert(isOutcome(winner(handA, handB)))));
forall(UInt, (hand) = >
assert(winner(hand, hand) == DRAW));
constPlayer = { ... hasRandom,getHand: Fun([], UInt),
seeOutcome: Fun([UInt], Null),
informTimeout: Fun([], Null) };
constAlice = { ... Player,wager: UInt };
constBob = { ... Player,acceptWager: Fun([UInt], Null) };
const DEADLINE = 10;
export const main =
Reach.App(
{},
[Participant('Alice', Alice), Participant('Bob', Bob)],
(A, B) = > {
const informTimeout = () = > {
each([A, B], () = > {
interact.informTimeout(); }); };
A.only(() = > {
const wager = declassify(interact.wager); });
A.publish(wager)
.pay(wager);
commit();
B.only(() = > {
interact.acceptWager(wager); });
B.pay(wager)
.timeout(DEADLINE, () = > closeTo(A, informTimeout));
var outcome = DRAW;
invariant(balance() == 2 * wager && isOutcome(outcome) );
while ( outcome == DRAW ) {
commit();
A.only(() = > {
const _handA = interact.getHand();
const [_commitA, _saltA] = makeCommitment(interact, _handA);
const commitA = declassify(_commitA); });
A.publish(commitA)
.timeout(DEADLINE, () = > closeTo(B, informTimeout));
commit();
unknowable(B, A(_handA, _saltA));
B.only(() = > {
const handB = declassify(interact.getHand()); });
B.publish(handB)
.timeout(DEADLINE, () = > closeTo(A, informTimeout));
commit();
A.only(() = > {
const [saltA, handA] = declassify([_saltA, _handA]); });
A.publish(saltA, handA)
.timeout(DEADLINE, () = > closeTo(B, informTimeout));
checkCommitment(commitA, saltA, handA);
outcome = winner(handA, handB);
continue; }
assert(outcome == A_WINS || outcome == B_WINS);
transfer(2 * wager).to(outcome == A_WINS ? A : B);
commit();
each([A, B], () = > {
interact.seeOutcome(outcome); });
exit(); });
Copy the code
Next is the JavaScript command line front end: Tu-8 /index.mjs
import { loadStdlib } from '@reach-sh/stdlib';
import * as backend from './build/index.main.mjs';
import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
(async() = > {const stdlib = await loadStdlib();
const isAlice = await ask(
`Are you Alice? `,
yesno
);
const who = isAlice ? 'Alice' : 'Bob';
console.log(`Starting Rock, Paper, Scissors! as ${who}`);
let acc = null;
const createAcc = await ask(
`Would you like to create an account? (only possible on devnet)`,
yesno
);
if (createAcc) {
acc = await stdlib.newTestAccount(stdlib.parseCurrency(1000));
} else {
const secret = await ask(
`What is your account secret? `,
(x= > x)
);
acc = await stdlib.newAccountFromSecret(secret);
}
let ctc = null;
const deployCtc = await ask(
`Do you want to deploy the contract? (y/n)`,
yesno
);
if (deployCtc) {
ctc = acc.deploy(backend);
const info = await ctc.getInfo();
console.log(`The contract is deployed as = The ${JSON.stringify(info)}`);
} else {
const info = await ask(
`Please paste the contract information:`.JSON.parse
);
ctc = acc.attach(backend, info);
}
const fmt = (x) = > stdlib.formatCurrency(x, 4);
const getBalance = async () => fmt(await stdlib.balanceOf(acc));
const before = await getBalance();
console.log(`Your balance is ${before}`);
constinteract = { ... stdlib.hasRandom }; interact.informTimeout =() = > {
console.log(`There was a timeout.`);
process.exit(1);
};
if (isAlice) {
const amt = await ask(
`How much do you want to wager? `,
stdlib.parseCurrency
);
interact.wager = amt;
} else {
interact.acceptWager = async (amt) => {
const accepted = await ask(
`Do you accept the wager of ${fmt(amt)}? `,
yesno
);
if (accepted) {
return;
} else {
process.exit(0); }}; }const HAND = ['Rock'.'Paper'.'Scissors'];
const HANDS = {
'Rock': 0.'R': 0.'r': 0.'Paper': 1.'P': 1.'p': 1.'Scissors': 2.'S': 2.'s': 2}; interact.getHand =async() = > {const hand = await ask(`What hand will you play? `.(x) = > {
const hand = HANDS[x];
if ( hand == null ) {
throw Error(`Not a valid hand ${hand}`);
}
return hand;
});
console.log(`You played ${HAND[hand]}`);
return hand;
};
const OUTCOME = ['Bob wins'.'Draw'.'Alice wins'];
interact.seeOutcome = async (outcome) => {
console.log(`The outcome is: ${OUTCOME[outcome]}`);
};
const part = isAlice ? backend.Alice : backend.Bob;
await part(ctc, interact);
const after = await getBalance();
console.log(`Your balance is now ${after}`); done(); }) ();Copy the code
Finally, there is the Web front end: Tu-9 /index.js
import React from 'react';
import AppViews from './views/AppViews';
import DeployerViews from './views/DeployerViews';
import AttacherViews from './views/AttacherViews';
import {renderDOM, renderView} from './views/render';
import './index.css';
import * as backend from './build/index.main.mjs';
import * as reach from '@reach-sh/stdlib/ETH';
const handToInt = {'ROCK': 0.'PAPER': 1.'SCISSORS': 2};
const intToOutcome = ['Bob wins! '.'Draw! '.'Alice wins! '];
const {standardUnit} = reach;
const defaults = {defaultFundAmt: '10'.defaultWager: '3', standardUnit};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {view: 'ConnectAccount'. defaults}; }async componentDidMount() {
const acc = await reach.getDefaultAccount();
const balAtomic = await reach.balanceOf(acc);
const bal = reach.formatCurrency(balAtomic, 4);
this.setState({acc, bal});
try {
const faucet = await reach.getFaucet();
this.setState({view: 'FundAccount', faucet});
} catch (e) {
this.setState({view: 'DeployerOrAttacher'}); }}async fundAccount(fundAmount) {
await reach.transfer(this.state.faucet, this.state.acc, reach.parseCurrency(fundAmount));
this.setState({view: 'DeployerOrAttacher'});
}
async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
selectAttacher() { this.setState({view: 'Wrapper'.ContentView: Attacher}); }
selectDeployer() { this.setState({view: 'Wrapper'.ContentView: Deployer}); }
render() { return renderView(this, AppViews); }}class Player extends React.Component {
random() { return reach.hasRandom.random(); }
async getHand() { // Fun([], UInt)
const hand = await new Promise(resolveHandP= > {
this.setState({view: 'GetHand'.playable: true, resolveHandP});
});
this.setState({view: 'WaitingForResults', hand});
return handToInt[hand];
}
seeOutcome(i) { this.setState({view: 'Done'.outcome: intToOutcome[i]}); }
informTimeout() { this.setState({view: 'Timeout'}); }
playHand(hand) { this.state.resolveHandP(hand); }}class Deployer extends Player {
constructor(props) {
super(props);
this.state = {view: 'SetWager'};
}
setWager(wager) { this.setState({view: 'Deploy', wager}); }
async deploy() {
const ctc = this.props.acc.deploy(backend);
this.setState({view: 'Deploying', ctc});
this.wager = reach.parseCurrency(this.state.wager); // UInt
backend.Alice(ctc, this);
const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null.2);
this.setState({view: 'WaitingForAttacher', ctcInfoStr});
}
render() { return renderView(this, DeployerViews); }}class Attacher extends Player {
constructor(props) {
super(props);
this.state = {view: 'Attach'};
}
attach(ctcInfoStr) {
const ctc = this.props.acc.attach(backend, JSON.parse(ctcInfoStr));
this.setState({view: 'Attaching'});
backend.Bob(ctc, this);
}
async acceptWager(wagerAtomic) { // Fun([UInt], Null)
const wager = reach.formatCurrency(wagerAtomic, 4);
return await new Promise(resolveAcceptedP= > {
this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
});
}
termsAccepted() {
this.state.resolveAcceptedP();
this.setState({view: 'WaitingForTurn'});
}
render() { return renderView(this, AttacherViews); }
}
renderDOM(<App />);
Copy the code
We wrote 88 lines of Reach and had two different front ends, with the command line version consisting of 111 lines of JavaScript for 199 lines. The Web version consists of 96 lines of JavaScript code for a total of 184 lines. Behind this, Reach generates 341 lines of Solidity code (see file: TU-8 /build/index.main.sol) and 1621 lines of TEAL code (see file: Tu-8 /build/index.main.mjs#L584), and 1586 lines of JavaScript code (see file: Tu-8 /build/index.main.mjs). If we hadn’t used Reach, we would have had to write these 3,553 lines of code ourselves and make sure they were updated synchronously with every change to the application. Now that you’ve seen the full Reach application from start to finish, it’s time to start developing your own! You may want to start with the workshop, which is a self-study course to practice and learn Reach through different specific projects. Alternatively, you can learn some of the concepts and background used in the Reach project. Or, perhaps dive into resources to explore the features of Reach. Whatever you’re up to next, we hope you’ll join our Discord community. Once you’ve joined the Discord community, send the message “@team, I just completed the tutorial!” , we’ll give you a tutorial veteran role so you can help others learn this tutorial! Thank you for having a great time learning this course with us!