raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
338 lines • 74.9 kB
HTML
<!doctype html>
<html class="default no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>raiden-ts - v3.1.1</title>
<meta name="description" content="Documentation for raiden-ts - v3.1.1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="assets/css/main.css">
<link rel="stylesheet" href="assets/css/pages.css">
<script async src="assets/js/search.js" id="search-script"></script>
</head>
<body>
<header>
<div class="tsd-page-toolbar">
<div class="container">
<div class="table-wrap">
<div class="table-cell" id="tsd-search" data-index="assets/js/search.json" data-base=".">
<div class="field">
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
<input id="tsd-search-field" type="text" />
</div>
<ul class="results">
<li class="state loading">Preparing search index...</li>
<li class="state failure">The search index is not available</li>
</ul>
<a href="index.html" class="title">raiden-ts - v3.1.1</a>
</div>
<div class="table-cell" id="tsd-widgets">
<div id="tsd-filter">
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
<div class="tsd-filter-group">
<div class="tsd-select" id="tsd-filter-visibility">
<span class="tsd-select-label">All</span>
<ul class="tsd-select-list">
<li data-value="public">Public</li>
<li data-value="protected">Public/Protected</li>
<li data-value="private" class="selected">All</li>
</ul>
</div>
<input type="checkbox" id="tsd-filter-inherited" checked />
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
</div>
</div>
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
</div>
</div>
</div>
</div>
<div class="tsd-page-title">
<div class="container">
<h1>Project raiden-ts - v3.1.1</h1>
</div>
</div>
</header>
<div class="container container-main">
<div class="row">
<div class="col-8 col-content">
<div class="tsd-panel tsd-typography">
<h2 align="center">
<br/>
<a href='https://raiden.network/'><img
width='400px'
alt=''
src="https://user-images.githubusercontent.com/35398162/54018436-ee3f6300-4188-11e9-9b4e-0666c44cda53.png" /></a>
<br/>
Raiden Light Client SDK Development
<br/>
</h2>
<h4 align="center">
Architecture description, code style and patterns, tips & tricks, caveats, typing and pits to avoid!
</h4>
<p>The Raiden Light Client SDK requires a Web3 provider like <a href="https://metamask.io">MetaMask</a>, <a href="https://www.parity.io">Parity</a> or <a href="https://geth.ethereum.org">Geth</a> and is built on the following concepts and libraries:</p>
<ul>
<li>Functional programming</li>
<li><a href="https://redux.js.org">Redux</a> architecture</li>
<li>Strictly typed data models aided by <a href="https://github.com/gcanti/io-ts">io-ts</a></li>
<li><a href="https://rxjs.dev/">RxJS</a> Observables asynchronous tasks through <a href="https://redux-observable.js.org">redux-observable</a> Epics</li>
<li>Off-chain communication through <a href="https://webrtc.org">WebRTC</a> and <a href="https://matrix.org">Matrix</a> servers using the <a href="https://github.com/node-webrtc/node-webrtc">wrtc</a> and <a href="https://github.com/matrix-org/matrix-js-sdk">matrix-js-sdk</a> libraries</li>
<li><a href="https://pouchdb.com/">PouchDB</a> database for state storage/persistency</li>
</ul>
<p>Below is a detailed explanation of the SDK architecture as well as things to keep in mind when reading the code and writing new functionality.</p>
<a href="#table-of-contents" id="table-of-contents" style="color: inherit; text-decoration: none;">
<h2>Table of Contents</h2>
</a>
<ul>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li><a href="#architecture">Architecture</a><ul>
<li><a href="#vertical-stack">Vertical (Stack)</a></li>
<li><a href="#horizontal-folder-structure">Horizontal (Folder Structure)</a></li>
</ul>
</li>
<li><a href="#typing-system">Typing System</a><ul>
<li><a href="#branded-types">Branded types</a></li>
<li><a href="#a-note-about-io-ts-and-type-complexity">A note about <code>io-ts</code> and type complexity</a></li>
</ul>
</li>
<li><a href="#public-api">Public API</a></li>
<li><a href="#actions">Actions</a></li>
<li><a href="#reducers">Reducers</a></li>
<li><a href="#epics">Epics</a><ul>
<li><a href="#hints-tips--tricks-and-pits-to-keep-an-eye-on-developing-epics">Hints, tips & tricks and pits to keep an eye on developing epics:</a></li>
</ul>
</li>
<li><a href="#testing">Testing</a><ul>
<li><a href="#unit-tests">Unit tests</a></li>
<li><a href="#e2e-tests">e2e tests</a></li>
</ul>
</li>
<li><a href="#debugging">Debugging</a><ul>
<li><a href="#browserlive-session">Browser/live session:</a></li>
<li><a href="#with-tests">With tests</a></li>
</ul>
</li>
</ul>
<a href="#architecture" id="architecture" style="color: inherit; text-decoration: none;">
<h2>Architecture</h2>
</a>
<p>In this section we will dive into the the internal machinery of the SDK and outline how RxJS, Redux and Epics work together.</p>
<a href="#vertical-stack" id="vertical-stack" style="color: inherit; text-decoration: none;">
<h3>Vertical (Stack)</h3>
</a>
<p>Instead of using classes as in object-oriented programming the SDK is written in a functional way and uses functions and type schemas like interfaces to separate logic and data.</p>
<p>The main entrypoint in the SDK is the <a href="classes/raiden.html">Raiden</a>, which provides access to all functionality. It is instantiated through the <a href="classes/raiden.html#create"><code>async Raiden.create</code></a> static method. This method returns a ready-to-use <code>Raiden</code> client object which instantiates and starts a central Redux <code>store</code>.</p>
<p>The Redux <code>store</code> is responsible for handling the <strong>actions</strong> that change the <strong>state</strong> through the <strong>reducers</strong>.</p>
<p>All <strong>actions</strong> go through the <strong>Epics</strong> (observables factory functions) middleware where synchronous and asynchronous tasks can be performed with the help of <strong>observables</strong>. Any new action that is output gets fed back to the Redux store and continues down this <em>actions pipeline</em>.</p>
<p>The <code>Raiden</code> client dispatches <strong>request</strong> actions to the <code>store</code> and waits for a respective <strong>success</strong> or <strong>failure</strong> to flow through the actions pipeline. These actions are created using <a href="https://github.com/raiden-network/light-client/blob/9abc8aac99b800ffbd127a6edb278b653fc9a450/raiden-ts/src/utils/actions.ts#L68">createAction</a> and <a href="https://github.com/raiden-network/light-client/blob/master/raiden-ts/src/utils/actions.ts#L218">createAsyncAction</a> with the <a href="https://github.com/redux-utilities/flux-standard-action">Flux Standard Action</a> schema/pattern.</p>
<p>All the business logic is contained in <code>Epics</code>. They accept a dependencies object as third parameter, which acts as a dependency injector (DI) and contains everything from configuration variables to the Ethers <code>Provider</code> instance. The <code>Epics</code> are factories that receive the <code>actions$</code> and <code>state$</code> observables and dependencies and output cold observables of new actions. They can listen and react to any kind of event or observable once subscribed.</p>
<p>The Blockchain interaction happens through <a href="https://github.com/ethers-io/ethers.js">ethers</a>. <a href="https://github.com/ethereum-ts/TypeChain">TypeChain</a> runs at build time and generates type declarations for the contracts. Both <code>Ethers</code> and <code>TypeChain</code> are available as dependencies at construction time.</p>
<p>Matrix interaction is done through <code>matrix-js-sdk</code>, but as it needs more complex async initialization (password signing, syncing, etc), it uses a initialization time epic which then makes the <code>MatrixClient</code> instance available through a <code>matrix$: AsyncSubject</code> dependency.</p>
<p>A visual representation of the inner architecture:</p>
<pre><code><span style="color: #000000"> </span><span style="color: #001080">user</span><span style="color: #000000"> </span><span style="color: #001080">method</span><span style="color: #000000"> </span><span style="color: #001080">calls</span>
<span style="color: #000000"> |</span>
<span style="color: #000000"> +--</span><span style="color: #001080">v</span><span style="color: #000000">-------------+</span>
<span style="color: #000000"> | |</span>
<span style="color: #000000"> | </span><span style="color: #001080">Raiden</span><span style="color: #000000"> | </span><span style="color: #001080">dependencies</span>
<span style="color: #000000"> | +------------------------+</span>
<span style="color: #000000"> +-+--------------+ |</span>
<span style="color: #000000"> | </span><span style="color: #001080">redux</span><span style="color: #000000"> </span><span style="color: #001080">store</span><span style="color: #000000"> |</span>
<span style="color: #000000"> | +-------------------------------+ |</span>
<span style="color: #000000"> | | | |</span>
<span style="color: #000000"> </span><span style="color: #001080">v</span><span style="color: #000000"> | </span><span style="color: #001080">action$</span><span style="color: #000000"> +-----------+ | |</span>
<span style="color: #000000"> +---+----------+---->+ </span><span style="color: #001080">reducer</span><span style="color: #000000"> | | |</span>
<span style="color: #000000"> ^ | | +-----+-----+ | |</span>
<span style="color: #000000"> | | | | | |</span>
<span style="color: #000000"> | +----------+-----------+--------+ |</span>
<span style="color: #000000"> | | | |</span>
<span style="color: #000000"> | | |</span><span style="color: #001080">state$</span><span style="color: #000000"> |</span>
<span style="color: #000000"> | | | |</span>
<span style="color: #000000"> | +----</span><span style="color: #001080">v</span><span style="color: #000000">-----------</span><span style="color: #001080">v</span><span style="color: #000000">------------</span><span style="color: #001080">v</span><span style="color: #000000">--+</span>
<span style="color: #000000"> | | |</span>
<span style="color: #000000"> | | </span><span style="color: #001080">Epics</span><span style="color: #000000"> |</span>
<span style="color: #000000"> | | |</span>
<span style="color: #000000"> | +------+--------^-------------^--+</span>
<span style="color: #000000"> | | | |</span>
<span style="color: #000000"> +--------<-------+ | |</span>
<span style="color: #000000"> </span><span style="color: #001080">output</span><span style="color: #000000"> </span><span style="color: #001080">actions</span><span style="color: #000000"> +-----</span><span style="color: #001080">v</span><span style="color: #000000">------+ +--</span><span style="color: #001080">v</span><span style="color: #000000">-----------+</span>
<span style="color: #000000"> | | | |</span>
<span style="color: #000000"> | </span><span style="color: #0070C1">Ξ</span><span style="color: #000000"> </span><span style="color: #001080">ethers</span><span style="color: #000000"> | | </span><span style="color: #001080">Transport</span><span style="color: #000000"> |</span>
<span style="color: #000000"> | | | |</span>
<span style="color: #000000"> +------------+ +--------------+</span>
</code></pre>
<a href="#horizontal-folder-structure" id="horizontal-folder-structure" style="color: inherit; text-decoration: none;">
<h3>Horizontal (Folder Structure)</h3>
</a>
<p>The project is structured in a domain-driven logic, each folder under <code>src</code> represents a semantic domain and should depend on the things inside of it as much as possible. Especially for <em>actions</em>*<em>, _reducers</em>, <em>epics</em>, <em>state</em> and specific *functions*.</p>
<ul>
<li><code>abi</code>, <code>contracts</code> and <code>deployment</code> are auto-generated data directories and don't contain any logic.<ul>
<li><code>abi</code> and <code>deployment</code> contains information about the deployed Raiden contracts for the Ethereum test networks, generated by <code>copyContracts.js</code> script at <code>prepare</code> time;</li>
<li><code>contracts</code> is the output of the <a href="https://github.com/ethereum-ts/TypeChain">TypeChain</a> contracts interfaces used for type safety, auto-generated from <code>abi</code>;</li>
</ul>
</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/utils"><code>utils</code></a> are common functions and types for the whole Light Client. The code here should not depend on any other SDK modules but can depend on external libraries.</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/channels"><code>channels</code></a> is the contracts logic for the blockchain and channel manager. This is mostly code for listening to events and perform blockchain actions like opening a channel, detecting a deposit or informing that a closed channel is settleable.</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/transport"><code>transport</code></a> is for the off-chain transport/communication. It is mainly matrix code, like login and initialization logic, room creation, invite and maintenance tasks as well as the actually sending and receiving of text messages between Raiden nodes; WebRTC is an optional faster p2p protocol, signaled through <code>matrix</code>, and also part of the transport;</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/transport"><code>messages</code></a> are the messages data models and validation and serialization/deserialization utils.</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/transfers"><code>transfers</code></a> contains the transfers and withdraw logic, life cycle and validation; withdraw is present here because as well as transfers, they may change a channel state, and must be done in a locked context;</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/services"><code>services</code></a> is logic related to interactions with <a href="https://github.com/raiden-network/raiden-services">Raiden services</a>.</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/db"><code>db</code></a> holds code responsible for syncing, saving and retrieving state from the persistence database;</li>
<li><a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src"><code>./src</code></a> contains the <code>Raiden</code> client and public API, root, Epic, Reducer, Action, specific types, constants etc. Anything that should be <em>global</em>.</li>
</ul>
<p>These are just suggestions on how to keep a well organized codebase. It is the developer's responsibility to decide in which module/domain any function, data or type belongs.</p>
<a href="#data-persistence" id="data-persistence" style="color: inherit; text-decoration: none;">
<h2>Data Persistence</h2>
</a>
<p>The visual representation of the data handling and persistance:</p>
<pre><code><span style="color: #000000"> +--------------------+</span>
<span style="color: #000000"> | </span><span style="color: #001080">State</span><span style="color: #000000"> |</span>
<span style="color: #000000"> .............. | [</span><span style="color: #001080">Redux</span><span style="color: #000000">] | <.............</span>
<span style="color: #000000"> . | (</span><span style="color: #267F99">in</span><span style="color: #000000"> </span><span style="color: #267F99">memory</span><span style="color: #000000">) | .</span>
<span style="color: #000000"> . +--------------------+ .</span>
<span style="color: #000000"> . ^ .</span>
<span style="color: #000000"> . . .</span>
<span style="color: #000000"> </span><span style="color: #267F99">async</span><span style="color: #000000"> </span><span style="color: #267F99">sync</span><span style="color: #000000"> </span><span style="color: #267F99">back</span><span style="color: #000000"> </span><span style="color: #267F99">load</span><span style="color: #000000"> </span><span style="color: #267F99">data</span><span style="color: #000000"> </span><span style="color: #267F99">on</span><span style="color: #000000"> </span><span style="color: #267F99">start</span><span style="color: #000000"> </span><span style="color: #267F99">read</span><span style="color: #000000"> </span><span style="color: #267F99">historic</span><span style="color: #000000"> </span><span style="color: #267F99">data</span>
<span style="color: #000000"> . . .</span>
<span style="color: #000000"> . . .</span>
<span style="color: #000000"> . +--------------------+ .</span>
<span style="color: #000000"> . | </span><span style="color: #267F99">Database</span><span style="color: #000000"> | .</span>
<span style="color: #000000"> .............> | [</span><span style="color: #001080">PouchDB</span><span style="color: #000000">] | ..............</span>
<span style="color: #000000"> |(</span><span style="color: #001080">persistent</span><span style="color: #000000"> </span><span style="color: #001080">storage</span><span style="color: #000000">)|</span>
<span style="color: #000000"> +--------------------+</span>
<span style="color: #000000"> ^</span>
<span style="color: #000000"> |</span>
<span style="color: #000000"> +------------------------+------------------------+</span>
<span style="color: #000000"> | | |</span>
<span style="color: #000000">+--------------------+ +--------------------+ +--------------------+</span>
<span style="color: #000000">| </span><span style="color: #001080">LevelDB</span><span style="color: #000000"> </span><span style="color: #001080">Adapter</span><span style="color: #000000"> | | </span><span style="color: #001080">IndexedDB</span><span style="color: #000000"> </span><span style="color: #001080">Adapter</span><span style="color: #000000"> | | </span><span style="color: #001080">Memory</span><span style="color: #000000"> </span><span style="color: #001080">Adapter</span><span style="color: #000000"> |</span>
<span style="color: #000000">| (</span><span style="color: #001080">NodeJS</span><span style="color: #000000">) | | (</span><span style="color: #001080">Web</span><span style="color: #000000">-</span><span style="color: #001080">Browser</span><span style="color: #000000">) | | (</span><span style="color: #001080">Tests</span><span style="color: #000000">) |</span>
<span style="color: #000000">+--------------------+ +--------------------+ +--------------------+</span>
</code></pre>
<p>Additional notes:</p>
<ul>
<li>The database adapter gets chosen based on the environment the SDK is running inside.</li>
<li>The hot state in memory with Redux is limiting its size by not including historic transfers data.</li>
<li>The data synchronization from the memory to the storage does not block the protocol logic and gets minimized by state diffs. It's performed by a <code>persister</code> middleware.</li>
<li>During shutdown/stop, the final data synchronization is been safely awaited for.</li>
<li>The database is used as source to do dump or upload backups (can be transferred between clients).</li>
<li>Old database scheme versions get automatically migrated during startup before the load to the Redux state.</li>
<li>Clients requests which try to read historic data might be slower due to the database read, while all protocol relevant operations are blazing fast, operating on in-memory data.</li>
</ul>
<a href="#typing-system" id="typing-system" style="color: inherit; text-decoration: none;">
<h2>Typing System</h2>
</a>
<p>TypeScript helps us check for correctness and aid implementation, validation and integration of various parts of the codebase. We can tell TypeScript what type of argument, return value or variable is expected in a function which helps us avoid passing wrong types when writing our code, like passing a number to a function that expects a string.</p>
<p>However, when we are dealing with unknown data we cannot always be sure it matches our expectations. To bridge this gap we use the <a href="https://github.com/gcanti/io-ts"><code>io-ts</code></a> library.</p>
<p><code>io-ts</code> solves this by allowing you to create <strong>codecs</strong>: real runtime objects which are able to verify the expectations (e.g. type) of any data, validating it and type guarding your code. They're also able to decode data from some (usually a more primitive) input type to the expected runtime instance/value, as well as encode a value to a given output format. Better yet, each codec have an associated compile-time <strong>type</strong>, to tell TypeScript what the output data of a codec looks like, allowing TypeScript to do its magic without needing to declare twice your data structure (one for runtime validation, other for compile-time type checks).
Finally, codecs can be composed in almost any way supported by TypeScript, making it very powerful. Example:</p>
<pre><code class="language-typescript"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #0000FF">*</span><span style="color: #000000"> </span><span style="color: #AF00DB">as</span><span style="color: #000000"> </span><span style="color: #001080">t</span><span style="color: #000000"> </span><span style="color: #AF00DB">from</span><span style="color: #000000"> </span><span style="color: #A31515">'io-ts'</span><span style="color: #000000">;</span>
<span style="color: #0000FF">const</span><span style="color: #000000"> </span><span style="color: #0070C1">Data</span><span style="color: #000000"> = </span><span style="color: #001080">t</span><span style="color: #000000">.</span><span style="color: #795E26">type</span><span style="color: #000000">({ </span><span style="color: #001080">key:</span><span style="color: #000000"> </span><span style="color: #001080">t</span><span style="color: #000000">.</span><span style="color: #001080">number</span><span style="color: #000000"> }); </span><span style="color: #008000">// this is the codec, a real object</span>
<span style="color: #0000FF">type</span><span style="color: #000000"> </span><span style="color: #267F99">Data</span><span style="color: #000000"> = </span><span style="color: #267F99">t</span><span style="color: #000000">.</span><span style="color: #267F99">TypeOf</span><span style="color: #000000"><</span><span style="color: #0000FF">typeof</span><span style="color: #000000"> </span><span style="color: #001080">Data</span><span style="color: #000000">>; </span><span style="color: #008000">// this is the type for the above codec, actually: { key: number; }</span>
<span style="color: #0000FF">const</span><span style="color: #000000"> </span><span style="color: #0070C1">mydata</span><span style="color: #000000"> = </span><span style="color: #267F99">JSON</span><span style="color: #000000">.</span><span style="color: #795E26">parse</span><span style="color: #000000">(</span><span style="color: #A31515">'{ "key": 123 }'</span><span style="color: #000000">); </span><span style="color: #008000">// mydata type is any</span>
<span style="color: #0000FF">const</span><span style="color: #000000"> </span><span style="color: #0070C1">decoded</span><span style="color: #000000"> = </span><span style="color: #001080">Data</span><span style="color: #000000">.</span><span style="color: #795E26">decode</span><span style="color: #000000">(</span><span style="color: #001080">mydata</span><span style="color: #000000">); </span><span style="color: #008000">// decoded is Either an error (left) or the expected value (right)</span>
<span style="color: #AF00DB">if</span><span style="color: #000000"> (</span><span style="color: #001080">decoded</span><span style="color: #000000">.</span><span style="color: #795E26">isLeft</span><span style="color: #000000">()) </span><span style="color: #AF00DB">throw</span><span style="color: #000000"> </span><span style="color: #001080">decoded</span><span style="color: #000000">.</span><span style="color: #001080">value</span><span style="color: #000000">;</span>
<span style="color: #008000">// from now on, decoded.value is known to be of type Data, thus mydata.key is number</span>
<span style="color: #0000FF">const</span><span style="color: #000000"> </span><span style="color: #0070C1">sq</span><span style="color: #000000">: </span><span style="color: #267F99">number</span><span style="color: #000000"> = </span><span style="color: #267F99">Math</span><span style="color: #000000">.</span><span style="color: #795E26">pow</span><span style="color: #000000">(</span><span style="color: #001080">decoded</span><span style="color: #000000">.</span><span style="color: #001080">value</span><span style="color: #000000">.</span><span style="color: #001080">key</span><span style="color: #000000">, </span><span style="color: #098658">2</span><span style="color: #000000">);</span>
</code></pre>
<p>We use <code>io-ts</code> to validate unsafe data and provide strong guarantees, define our own codecs to serialize and deserialize custom objects, and provide typeguards to narrow broader types to more specific ones.</p>
<a href="#branded-types" id="branded-types" style="color: inherit; text-decoration: none;">
<h3>Branded types</h3>
</a>
<p>TypeScript branded types (aka. poor man's nominal typing) helps developers provide hints/refinements about specific types which can be compared to full inheritance systems in OOP paradigms. It consists basically of making a branded type <code>TB</code> as the intersection of base type <code>A</code> with some brand <code>B</code>, which makes the branded type more specific. So, <code>type TB = number & { brand }</code> is equivalent in OO of making an inherited/child class <code>TB</code> extending <code>number</code>. You can still pass <code>TB</code> where <code>number</code> is expected (and it's a child of number), but you can't pass a simple <code>number</code> where <code>TB</code> is expected unless you type-cast or decode/validate it as such.</p>
<p>On TypeScript, all this normally happens only at compile-time (the brand usually is just an interface with a <code>unique symbol</code>), having no impact at runtime, when the variable would effectivelly be a simple <code>number</code>. <code>io-ts</code> allows us to have codecs which also validate if a parent type matches the expectations to be considered a branded/child type, allowing us to also have specific type safety beyond just validating if some data is a <code>string</code> or not.</p>
<p>For example, in our <a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/utils/types.ts">types</a>, we declare the type <code>Address</code>, which is (actually) a <code>string</code>, which happens to be validated to also be a <code>HexString</code> of size <code>20 bytes</code>, and which also happens to be in the checksummed format, without ceasing to be a <code>string</code>!</p>
<p>We try to make all the types we use as strict as needed, specially considering externally-originated data, like messages, as the security of the system may depend on doing the right thing, always. With proper TypeScript usage and tools (like <code>io-ts</code> and type-safe actions), we should always be able to narrow down the data type we're using in any part of the code. Avoid acting on <code>any</code>-typed data, much less declaring it as so. Literals and tagged unions help a lot to keep narrow/strict types whenever needed, with <code>if</code>, <code>throw</code>, <code>typeof</code> and even explicit typeguard functions. This way, we'll get the compiler to help development and enforce correctness.</p>
<a href="#a-note-about-io-ts-and-type-complexity" id="a-note-about-io-ts-and-type-complexity" style="color: inherit; text-decoration: none;">
<h3>A note about <code>io-ts</code> and type complexity</h3>
</a>
<p>While developing our more complex types, we noticed a significant slowdown on TypeScript transpilation time (taking up to 5min), which also heavily affected <code>tsserver</code> for Intellisense, auto-completion, type validation, and also expressed itself as <code>.d.ts</code> declaration files of tens of MBs in size.</p>
<p>Further investigation have shown that this was caused by TypeScript somehow always inlining every reference to ALL type declarations exported as <code>type</code>, which was exponential on complex types as <code>RaidenState</code>, messages and actions, and peaked on the <code>epics</code>, which depends on all of this.</p>
<p>The fix was to use empty <code>interface</code>s inheriting from the local type wherever possible, which causes TypeScript to use the <code>import</code>ed declaration instead of inlining and duplicating the whole type tree on every reference.</p>
<pre><code class="language-typescript"><span style="color: #AF00DB">export</span><span style="color: #000000"> </span><span style="color: #0000FF">type</span><span style="color: #000000"> </span><span style="color: #267F99">RaidenState</span><span style="color: #000000"> = </span><span style="color: #267F99">t</span><span style="color: #000000">.</span><span style="color: #267F99">TypeOf</span><span style="color: #000000"><</span><span style="color: #0000FF">typeof</span><span style="color: #000000"> </span><span style="color: #001080">RaidenState</span><span style="color: #000000">>; </span><span style="color: #008000">// slow</span>
<span style="color: #AF00DB">export</span><span style="color: #000000"> </span><span style="color: #0000FF">interface</span><span style="color: #000000"> </span><span style="color: #267F99">RaidenState</span><span style="color: #000000"> </span><span style="color: #0000FF">extends</span><span style="color: #000000"> </span><span style="color: #267F99">t</span><span style="color: #000000">.</span><span style="color: #267F99">TypeOf</span><span style="color: #000000"><</span><span style="color: #0000FF">typeof</span><span style="color: #000000"> </span><span style="color: #001080">RaidenState</span><span style="color: #000000">> {} </span><span style="color: #008000">// fast</span>
</code></pre>
<p>This works on all types which members are known at compile time, and should be preferred wherever possible.</p>
<a href="#public-api" id="public-api" style="color: inherit; text-decoration: none;">
<h2>Public API</h2>
</a>
<p>The <code>Raiden</code> client class is the entrypoint for the public API. The focus of this class is Developer Experience and we should avoid exposing too much of its internals while still providing what is needed for every reasonable use case.</p>
<p>This balance is important to keep in mind. It is desirable that changes in the interface are as backwards compatible as possible.</p>
<p><code>Raiden</code> must have the bare minimum property list, relying on the state machine for any action performing, and being mostly responsible for translating the state machine logic to a more developer-friendly interface. That's why, instead of exposing the whole <code>action$</code> events pipeline and asking the user to interpret it, most state-changing methods actually return a <code>Promise</code>, and after dispatching the <code>request</code> action, translate the respective <code>success</code> or <code>fail</code> action to resolve or reject the returned promise.</p>
<p>For public interface simplicity and usability, a subset of the actions is exposed through an <code>events$</code> observable as an alternative to duplicating almost everything on explicit reimplementations using <code>EventEmitter</code>s or any other more common approach. <code>channels$</code> list is also exposed through a public observable mapped from <code>state$</code>. <code>state$</code> is publicly exposed for users willing to react to explicit state changes, although the database is required and must be persisted at all times;</p>
<p>Despite all of that, state machine correctness and simplicity is a priority over public API class implementation complexity. Therefore, non-state changing (aka read-only) methods may be implemented inside Raiden directly, or in pure separate utility functions when needed.</p>
<a href="#actions" id="actions" style="color: inherit; text-decoration: none;">
<h2>Actions</h2>
</a>
<p>We follow the <a href="https://github.com/redux-utilities/flux-standard-action">Flux Standard Action (<code>FSA</code>)</a> pattern when creating actions. They should always be defined in the same module as the reducer which handles them.</p>
<p>We implemented custom functions to generate actions, with the direct advantage of using <code>io-ts</code> codecs to make them more powerful. Those functions and helpers are defined in <a href="https://github.com/raiden-network/light-client/blob/master/raiden-ts/src/utils/actions.ts"><code>src/utils/actions.ts</code></a>.</p>
<ul>
<li><code>createAction</code> and <code>createAsyncAction</code> provide a simple way to create typesafe and serializable actions</li>
<li><code>createReducer</code> simplifies creation of reducers which can be extended with additional actions handlers</li>
</ul>
<p><code>FSA</code> actions may contain data in both <code>payload</code> and <code>meta</code> properties. As FSA convention dictates that on <code>error=true</code> case payload should be the <code>Error</code> which happened, the rule of thumb is to use <code>meta</code> for any data which may be needed to filter the error action going through (e.g. <code>{ tokenNetwork, partner }</code> on channel actions). It's also recommended to be consistent on <code>meta</code> on request/success/error-related actions. All other data should go on <code>payload</code> as usual.</p>
<a href="#reducers" id="reducers" style="color: inherit; text-decoration: none;">
<h2>Reducers</h2>
</a>
<p>Reducers are one of the simplest parts to write. Just some heads-up:</p>
<ul>
<li>State must be minimal. Put in state only what's really needed to be preserved across sessions, needed to reload the account on a different system, or very expensive to re-fetch on every run. Everything else is better to go on epic dependencies or stateful observable operators and events on a per-session basis.</li>
<li>As stated earlier, module/domain specific reducers should always as possible act only on actions declared on their module.</li>
<li>reducers should <strong>never</strong> mutate state directly. Use <code>...</code> spread operator and if needed <code>lodash/fp</code> module to get mutated copies of the state you're acting on. Even when using it, be careful with object references, as you may still be mutating state through a reference to a nested object.</li>
<li>Try to always be as type strict as possible. lodash's <code>get</code> and <code>set</code> usually aren't typesafe (because of their nested sweetness), so add explicit declarations and narrowing checks whenever needed, to be warned when changing anything that could break a reducer.</li>
<li>Don't overthink reducers: you can always assume you're receiving a valid/consistent state and action, so no need to add too much checks besides the typing ones, just be sure to always return a valid/consistent state as well and we should be good.</li>
<li>Each <code>action</code> type must be handled only once, in a single and very specific place, to avoid issues with the <code>reduce-reducers</code> pattern used in the root reducer declaration.</li>
<li>The state must be JSON-serializable. Try to stay on the primitive types as much as possible, unless there's a good reason to have an instance there, and it must be serializable (with custom <code>io-ts</code> codecs, like <code>BigNumber</code>). Our custom functions to create actionCreators helps here, as it already provide explicit <code>io-ts</code> codecs.</li>
</ul>
<a href="#epics" id="epics" style="color: inherit; text-decoration: none;">
<h2>Epics</h2>
</a>
<p>Epics are just functions, which receive 3 parameters: <code>action$</code>, <code>state$</code> and <code>deps</code>, and create a cold observable which, when subscribed, perform this epic's duties. They can choose to act on any action or state change, or even dependencies, but it's important they try to follow the UNIX philosophy: do one thing, and do it well. So, as much as possible, each epic should listen a single event type, and output only the minimal action set as outcome of its specific task. The <code>action$</code> input observable should be declared as <code>Observable<RaidenAction></code>, as every action goes through (although filtered as early as possible), but try to declare the output as specifically as possible, with a tagged union of any possible action output type.</p>
<a href="#hints-tips-amp-tricks-and-pitfalls-when-developing-epics" id="hints-tips-amp-tricks-and-pitfalls-when-developing-epics" style="color: inherit; text-decoration: none;">
<h3>Hints, tips & tricks and pitfalls when developing epics</h3>
</a>
<ul>
<li>Be careful to not complete the output observable if the system is still running, or its function will be lost, as subscriptions aren't restarted.</li>
<li>Any unhandled exception (which shouldn't happen in normal operation) will cause a <code>raidenShutdown</code> action which in turn triggers completion of the inputs (<code>action$</code> and <code>state$</code>). The individual epics then have <code>httpTimeout</code> to detect this completion and gracefully complete on their own once they finish their latest async tasks/teardown. During this time some output action may still go through and change state, but this this will only be received by epics later in the subscription queue (as earlier epics should already have completed, i.e. a serial completion mechanism signaled by completion of the input observables). After this timeout, if some epic didn't complete, they're logged and then unsubscribed. Only after that the database is flushed and closed.</li>
<li>Notice that catching the error in the first-level operator pipeline in an epic may prevent sdk's shutdown, but unless you're returning a long-lived/useful epic inside <code>catchError</code>, the main observable will already have completed/errored here, and whatever is above it will be noop on new/further actions; if you want to catch and handle an action, make sure to handle this action inside a <code>*Map</code> operator, and <code>catchError</code> by the end of it before returning values back to your top-level pipe.</li>
<li>If an epic acts directly (like a map) on <code>action$</code>, take care to filter early on the specific action you listen to and output a different action, or else your action output will feedback on your epic and cause an infinite loop. Same if you depend on a <code>state$</code> change and your output action causes the same state change that just went through.</li>
<li>A common epic pattern: <code>=> action$.pipe(filter(isActionOf(action)), withLatestFrom(state$), map((action, state) => ...))</code></li>
<li>Never subscribe explicitly inside an epic if not strictly necessary; maps and pipes help into getting a proper <em>action pipeline</em> where a single subscription is used for all epics, making the system more deterministic, declarative and performant.</li>
<li>Careful if you use <code>withLatestFrom</code> with <code>action$</code> or <code>state$</code> inside a <code>mergeMap</code>, <code>concatMap</code>, <code>exhaustMap</code>, etc, as the inner (returned) observable is created only when the outer value flows through and the callback of these operators is called. The use of <code>deps.latest$</code>, which is holding the current/latest emition of several reactive values (a <code>ReplaySubject(1)</code>), is safe in <code>withLatestFrom</code>.</li>
<li>This example showcases the problem mentioed above: <code>withLatestFrom</code> only starts "seeing" values of the <code>input$</code> after it's created <strong>and</strong> subscribed, and will discard any source value while the <code>input$</code> didn't fire at least once, meaning it can be a silent source of bugs when used inside these mapping operators. e.g. of problematic logic:</li>
</ul>
<pre><code class="language-js"><span style="color: #001080">action</span><span style="color: #000000">.</span><span style="color: #795E26">pipe</span><span style="color: #000000">(</span>
<span style="color: #000000"> </span><span style="color: #795E26">filter</span><span style="color: #000000">(...),</span>
<span style="color: #000000"> </span><span style="color: #795E26">mergeMap</span><span style="color: #000000">(</span><span style="color: #001080">action</span><span style="color: #000000"> </span><span style="color: #0000FF">=></span>
<span style="color: #000000"> </span><span style="color: #795E26">from</span><span style="color: #000000">(...).</span><span style="color: #795E26">pipe</span><span style="color: #000000">(</span>
<span style="color: #000000"> </span><span style="color: #795E26">withLatestFrom</span><span style="color: #000000">(</span><span style="color: #001080">state$</span><span style="color: #000000">),</span>
<span style="color: #000000"> </span><span style="color: #795E26">map</span><span style="color: #000000">((</span><span style="color: #001080">_</span><span style="color: #000000">, </span><span style="color: #001080">state</span><span style="color: #000000">) </span><span style="color: #0000FF">=></span><span style="color: #000000"> ...), </span><span style="color: #008000">// will swallow input while state$ doesn't see a new state after mergeMap call</span>
<span style="color: #000000"> ),</span>
<span style="color: #000000"> ),</span>
<span style="color: #000000">);</span>
</code></pre>
<ul>
<li>In the spirit of tips above, you should ALWAYS know when your (specially inner) observables are <strong>created</strong>, <strong>subscribed</strong> and <strong>unsubscribed</strong>.<ul>
<li>On the outer/top level observable (the one returned by the epic), the creation and subscription is performed at the moment the SDK is instantiated, and unsubscription happens if the observable completes, errors or at SDK stop/shutdown.</li>
<li><code>mergeMap</code> creates the inner observable when the a value goes through, and subscribes to it immediatelly. completing the inner observable won't complete the outer, but unhandled errors do error the outer observable.</li>
<li><code>concatMap</code> <strong>creates</strong> the inner observable also at the exact moment a value goes through, but its subscription is only made when the previous observable completes. Keep an eye if the values you depended on at creation time are still valid/up-to-date at subscription time. Use <code>defer</code> if needed.</li>
<li><code>exhaustMap</code> is like <code>concatMap</code>, but instead of queueing every output observable serially, it <strong>ignores</strong> the value if the previous subscription is still on, and outputs the next inner observable only on next value going through after previous observable completed.</li>
</ul>
</li>
<li>Obvious but it's worth to mention: if you handle external/unsafe data, use proper data validation through <code>io-ts</code>. Distrust everything!</li>
</ul>
<a href="#latestdepslatest" id="latestdepslatest" style="color: inherit; text-decoration: none;">
<h3><code>Latest</code>/<code>deps.latest$</code>:</h3>
</a>
<p>The <code>withLatestFrom</code> issue mentioned above caused a pattern to emerge: <code>multicast</code>ing outer input values (usually mapped) to some <code>ReplaySubject(1)</code>, which were then used in inner observables which depended on having a way to fetch the latest values which may have been calculated even before the inner ones got subscribed. To avoid repeating this again and again, creating a callback-hell of nested observables and multicasted subjects, we decided to collect some of these more relevant values on a single, central subject, kept on the epics dependencies: <code>deps.latest$</code>.</p>
<p>This subject gets populated by a special <code>latestEpic</code>, which is the first one to receive actions/state changes notifications, and is the last one to be unsubscribed; it then maps the actions and keeps relevant values in a single object, which gets updated when any of these values change.</p>
<p>Example: if you need to map some action/observable and consult the reactive state, but ensure you react/test as soon as possible with the latest state, you can use <code>withLatestFrom(latest$.pipe(pluck('state')))</code> operator to get the latest state ever, but also be reactive if it changes while this inner observable is hot.</p>
<p>This object's interface/schema is kept in the <code>Latest</code> type. If you want to react only to changes to a specific property of this object, <code>pluckDistinct</code> custom operator integrates <code>pluck</code> and <code>distinctUntilChanged</code>.</p>
<p>Be careful when adding new properties to this object, as often this is something used only once, and may as well be reactively fetched from the inputs as usual; in any case though, ensure your code is always reactive and using the latest values of any property/state, instead of static references to values which may be outdated.</p>
<a href="#testing" id="testing" style="color: inherit; text-decoration: none;">
<h2>Testing</h2>
</a>
<p>The SDK tests are located at the <a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests">raiden-ts/tests</a> subfolder. The testing framework used is <a href="http://jestjs.io">jest</a>, and the complete suite can be run with <code>yarn test</code> command in the SDK root folder. This will run both <a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests/unit">unit</a> and <a href="https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests/integration">integration</a> tests (files ending with <code>.spec.ts</code>), and collect coverage in the <code>raiden/.coverage</code> folder. Keeping an eye on <code>raiden/.coverage/lcov-report/index.html</code> during test writting can be a good guide to writing them, although just covering the lines isn't enough, and some thought must be put into imagining possible scenarios, invariants, critical and edge states, and testing them throughout the respective tests.</p>
<a href="#unit-tests" id="unit-tests" style="color: inherit; text-decoration: none;">
<h3>Unit tests</h3>
</a>
<p>The unit tests try to cover as much as possible the individual functions, by testing behavior through expected and unexpected inputs, and their respective outputs. For that, we use extensively <a href="https://jestjs.io/docs/en/mock-functions">jest mocks</a> to contextually replace external and internal dependencies of each tested function, and force the external logic t