On this page:
1.1 Decentralized applications
1.2 A minial Reach program
1.3 Compile
1.4 Verify
1.5 Interface
1.6 Execute
1.7 Next steps
0.1.2

1 Overview

This is an informal overview of Reach and the structure of a Reach program. The goal of this document is to give enough technical specifics to help you understand what Reach does, but it isn’t intended as either a tutorial or a reference. When you’re ready to really begin a project, you can start with one of those, or the workshop.

1.1 Decentralized applications

DApps are made of multiple agents interacting with each other through some backend consensus network, like Ethereum or Algorand. These agents act on behalf of principals that provide direction and authority through information. These principals might be humans or other autonomous agents or even committees and organizations with their own structure. The consensus network allows these agents to transfer and receive value in the form of network-specific tokens, like ETH or ALGO. The network also allows the creation of "contracts" that ensure that all agents follow the same rules as they take turns computing and publishing values and information. The details of these "contracts" are specific to each consensus network, but they are implicitly trusted by all agents and principals, because their operation can be independently verified to match the previously agreed-upon rules.

A single Reach program incorporates all aspects of a DApp:
  • Participant backends are the agents acting on behalf of the principals.

  • Frontends are the technical representation of the interface between the participants and the principals.

  • A contract enforces the rules of the program, including the order of operation.

In Reach, a programmer only needs to specify the actions of participantswhat they do individually and what they do in unison. The Reach compiler automatically derives a contract for the consensus network via a connector that enforces these rules.

1.2 A minial Reach program

Let’s look at a simple Reach program where two principals, Alice and Bob, interact. In this DApp, Alice has some information that Bob might want and she has an amount of network tokens in mind that she’d like to trade for it.

You can look at the entire example program by visiting overview/index.rsh.

The main part of the program looks like this:

 1    'reach 0.1';
 2    
 3    export const main =
 4      Reach.App(
 5        {},
 6        [['Alice', { request: UInt256,
 7                     info: Bytes }],
 8         [  'Bob', { want: Fun([UInt256], Null),
 9                     got: Fun([Bytes], Null) }]],
10        (A, B) => {
..          // ...body...
30        } );

The elided lines, 11 through 29, contain the body of the application, which we can divide into four parts.

..    // ...
11    A.only(() => {
12      const request = declassify(interact.request); });
13    A.publish(request);
14    commit();
..    // ...

At this point, Bob’s backend has learned the value of request and can deliver it to Bob’s frontend for his approval. This happens next.

..    // ...
16    B.only(() => {
17      interact.want(request); });
18    B.pay(request);
19    commit();
..    // ...

It’s now Alice’s turn again,

..    // ...
21    A.only(() => {
22      const info = declassify(interact.info); });
23    A.publish(info);
24    transfer(request).to(A);
25    commit();
..    // ...

The only thing left is for Bob’s backend to deliver the information to his frontend.

..    // ...
27    B.only(() => {
28      interact.got(info); });
29    exit();
..    // ...

Reach programmers don’t need to think about details like contract storage, protocol diagrams, state validation, or network details; instead, they can focus exclusively on the business logic of their application.

1.3 Compile

After a Reach programmer writes this application in a file like overview/index.rsh, they could run

  $ reach compile overview/index.rsh

and the build directory will contain a new file named index.main.mjs, which contains a JavaScript implementation of a backend for each participant, as well as the Ethereum bytecode for the contract.

If you are curious, you can take a look at this file by going to overview/build/index.main.mjs. The Ethereum bytecode is not readable, but if you understand Solidity, you may want to look at overview/build/index.main.sol to see the original Solidity source that it is compiled from. Reach can leave files like these in place when run with --intermediate-files.

For this 30 line application, the Reach compiler generated 81 lines of JavaScript code in two functions, one for Alice and one for Bob. Separately, it generated 67 lines of Solidity code to implement the contract. If a programmer wasn’t using Reach, they would have to write these 148 lines in these three modules separately and keep them synchronized at every step of the development process.

Morever, Reach doesn’t only work for Ethereum: it is blockchain agnostic and can be easily configured to use a different connector to target other consensus networks, like Algorand. Nor is Reach tied to JavaScript, it can be configured to target other backend languages, like Go.

1.4 Verify

Reach doesn’t just compile your program, it also verifies it and ensures that entire categories of errors don’t occur. For example, it always guarantees that the balance in the contract at the end of the program is zero. This is important, because if it were not true, then tokens would be locked away by the contract and inaccessible.

For this example program, it is obvious that when a single transfer of request goes in at line 18 and a single transfer of request goes out at line 24, then the balance is zero at the end of the program. We could make a small tweak, however, to demonstrate things going wrong.

Let’s change the third step to leave a single unit in the balance:

..    // ...
21    A.only(() => {
22      const info = declassify(interact.info); });
23    A.publish(info);
24    transfer(request-1).to(A); // <--- Oops!
25    commit();
..    // ...

And then run the compiler

  $ reach compile overview/index-error.rsh

It will print out a detailed error message showing the violation.

..    // ...

 2    Verifying knowledge assertions

 3    Verifying with mode = VM_Honest

 4    Verification failed:

 5      in VM_Honest mode

 6      of theorem TBalanceZero

 7      at ./overview/index-error.rsh:29:11:application

 8    

 9      Theorem formalization:

10      (= ctc_balance4 0 )

11    

12      This could be violated if...

13        "interact_Alice_request" = 1

..    // ...

Verification failures include a lot of information, such as a concrete counter-example showing values that could have been provided by frontends that would lead to the property failing to hold. In this case, it reports that if Alice were to pass an interact.request over 1 at the start of the program on line 4, then the balance of the contract would not be provably 0 at the end of the program.

Reach programmers don’t need to be worried about entire categories of errors, because the compiler automatically checks their code and ensures that those errors aren’t present. Of course, there’s a lot more to say about the details of automatic verification; indeed, it is one of the most powerful features of Reach, but we’ll leave it at that for now.

1.5 Interface

The backend produced by the Reach compiler isn’t an application on its own. In particular, each participant needs a frontend to interact with. In a real deployment, this interfacing code would be tied to a GUI, like a Web or smartphone app. Let’s look at a simple command-line version that demonstrates how it would work for testing on a private devnet.

You can look at the entire example interface program by visiting overview/index.mjs.

The program is just 23 lines long and the shell of it is quite simple:

 1    import * as stdlib from '@reach-sh/stdlib/ETH.mjs';
 2    import * as backend from './build/index.main.mjs';
 3    
 4    ( async () => {
 5      const toNetworkFormat = (n) => stdlib.toWeiBigNumber(n, 'ether');
 6    
 7      const accAlice = await stdlib.newTestAccount(toNetworkFormat('5'));
 8      const accBob = await stdlib.newTestAccount(toNetworkFormat('10'));
 9    
10      const ctcAlice = await accAlice.deploy(backend);
11      const ctcBob = await accBob.attach(backend, ctcAlice);
12    
13      await Promise.all([
..        // ...
22      ]);
23    })();

This code will be very similar for all testing programs and demonstrates how straight-forward it is to scaffold a Reach application for testing.

Let’s look at initializing and interfacing each participant, starting with Alice.

..    // ...
14    backend.Alice(
15      stdlib, ctcAlice,
16      { request: toNetworkFormat('5'),
17        info: 'If you wear these, you can see beyond evil illusions.' }),
..    // ...

Let’s look at Bob next.

..    // ...
18    backend.Bob(
19      stdlib, ctcBob,
20      { want: (amt) => console.log(`Alice asked Bob for ${amt}`),
21        got: (secret) => console.log(`Alice's secret is: ${stdlib.hexToString(secret)}`) })
..    // ...

Reach completely abstracts all the details of the chosen consensus network from the programmer, except for those directly impinging on business decisions, like the amounts of currency transacted. Reach allows programmers to focus on the business logic of their application at every stage, from the core application to the interfacing elements.

1.6 Execute

It’s now time to execute this test program and ensure that everything is working correctly. In this case, we’ve set up our application simply: there’s one Reach file for the application and one JavaScript file for the interface. This is a common practice, so Reach comes with a simple wrapper script to build and execute such applications. We just run:

  $ reach run

And then Reach

On typical developer laptops, this entire process takes seconds and can be completely integrated into existing development IDEs, like VSCode, so Reach developers can compile, verify, build, launch, and test their Reach app with a single command.

Reach completely abstracts all the details of building and maintaining consensus network test environments and build scripts from the programmer, so they can focus exclusively on the business logic of their application.

1.7 Next steps

In this overview, we’ve briefly described the structure and fundamental concepts of a Reach application. We’ve shown how to construct a simple program, compile it, connect an interface, and run it. Since this is only a brief overview of what Reach can do, we left a lot out. But even so, it is should be clear why Reach is easiest and safest programming language for decentralized application development.

Furthermore, this program has many flaws and should not be used in practice. For example, it provides no protection to Bob in the event that Alice fails to deliver the information and makes no attempt to ensure that the information is what he wants. Reach allows you to abstract away the low-level details of your decentralized program and focus on these sorts of bigger picture issues. In the rest of the guide, we discuss design issues like this. For example,

However, unless you’re ready to dive deep now, the next steps for you are to:

Thanks for being part of Reach!