@parity/light.js
Version:
A high-level reactive JS library optimized for light clients
1,107 lines (505 loc) • 34.5 kB
HTML
<html lang="" >
<head>
<meta charset="UTF-8">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Light Client Development · GitBook</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="">
<meta name="generator" content="GitBook 3.2.3">
<link rel="stylesheet" href="../gitbook/style.css">
<link rel="stylesheet" href="../gitbook/gitbook-plugin-highlight/website.css">
<link rel="stylesheet" href="../gitbook/gitbook-plugin-search/search.css">
<link rel="stylesheet" href="../gitbook/gitbook-plugin-fontsettings/website.css">
<meta name="HandheldFriendly" content="true"/>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="../gitbook/images/apple-touch-icon-precomposed-152.png">
<link rel="shortcut icon" href="../gitbook/images/favicon.ico" type="image/x-icon">
<link rel="next" href="reactive-programming.html" />
</head>
<body>
<div class="book">
<div class="book-summary">
<div id="book-search-input" role="search">
<input type="text" placeholder="Type to search" />
</div>
<nav role="navigation">
<ul class="summary">
<li class="chapter " data-level="1.1" data-path="../">
<a href="../">
Introduction
</a>
</li>
<li class="chapter " data-level="1.2" >
<span>
Getting started
</span>
<ul class="articles">
<li class="chapter " data-level="1.2.1" data-path="../getting-started/installation.html">
<a href="../getting-started/installation.html">
Installation
</a>
</li>
<li class="chapter " data-level="1.2.2" data-path="../getting-started/does-it-work-with-a-full-node.html">
<a href="../getting-started/does-it-work-with-a-full-node.html">
Does this work with a full node?
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.3" >
<span>
Guides
</span>
<ul class="articles">
<li class="chapter " data-level="1.3.1" data-path="../guides/tutorial1-set-up-a-light-client.html">
<a href="../guides/tutorial1-set-up-a-light-client.html">
Tutorial Part 1: Set up a Light Client
</a>
</li>
<li class="chapter " data-level="1.3.2" data-path="../guides/tutorial2-our-first-rpcobservable.html">
<a href="../guides/tutorial2-our-first-rpcobservable.html">
Tutorial Part 2: Our First RpcObservable
</a>
</li>
<li class="chapter " data-level="1.3.3" data-path="../guides/tutorial3-manipulating-rpcobservables.html">
<a href="../guides/tutorial3-manipulating-rpcobservables.html">
Tutorial Part 3: Manipulating RpcObservables
</a>
</li>
<li class="chapter " data-level="1.3.4" data-path="../guides/tutorial4-send-a-transaction.html">
<a href="../guides/tutorial4-send-a-transaction.html">
Tutorial Part 4: Send a Transaction
</a>
</li>
<li class="chapter " data-level="1.3.5" data-path="../guides/tutorial5-work-with-contracts.html">
<a href="../guides/tutorial5-work-with-contracts.html">
Tutorial Part 5: Work with Contracts
</a>
</li>
<li class="chapter " data-level="1.3.6" data-path="../guides/tutorial6-integrate-with-react.html">
<a href="../guides/tutorial6-integrate-with-react.html">
Tutorial Part 6: Integrate with React
</a>
</li>
<li class="chapter " data-level="1.3.7" data-path="../guides/redux-integration.md">
<span>
Redux Integration
</a>
</li>
<li class="chapter " data-level="1.3.8" data-path="../guides/angular-integration.md">
<span>
Angular Integration
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.4" >
<span>
Concepts
</span>
<ul class="articles">
<li class="chapter active" data-level="1.4.1" data-path="light-client-development.html">
<a href="light-client-development.html">
Light Client Development
</a>
</li>
<li class="chapter " data-level="1.4.2" data-path="reactive-programming.html">
<a href="reactive-programming.html">
Reactive Programming
</a>
</li>
<li class="chapter " data-level="1.4.3" data-path="rpc-observables.html">
<a href="rpc-observables.html">
RpcObservables
</a>
</li>
<li class="chapter " data-level="1.4.4" data-path="rpc-observables-properties.html">
<a href="rpc-observables-properties.html">
RpcObservables Properties
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5" >
<span>
API
</span>
<ul class="articles">
<li class="chapter " data-level="1.5.1" data-path="../api/modules/_api_.html">
<a href="../api/modules/_api_.html">
api
</a>
</li>
<li class="chapter " data-level="1.5.2" >
<span>
FrequencyObservables
</span>
<ul class="articles">
<li class="chapter " data-level="1.5.2.1" data-path="../api/modules/_frequency_accounts_.html">
<a href="../api/modules/_frequency_accounts_.html">
frequency/accounts
</a>
</li>
<li class="chapter " data-level="1.5.2.2" data-path="../api/modules/_frequency_blocks_.html">
<a href="../api/modules/_frequency_blocks_.html">
frequency/blocks
</a>
</li>
<li class="chapter " data-level="1.5.2.3" data-path="../api/modules/_frequency_frequency_.html">
<a href="../api/modules/_frequency_frequency_.html">
frequency/frequency
</a>
</li>
<li class="chapter " data-level="1.5.2.4" data-path="../api/modules/_frequency_health_.html">
<a href="../api/modules/_frequency_health_.html">
frequency/health
</a>
</li>
<li class="chapter " data-level="1.5.2.5" data-path="../api/modules/_frequency_other_.html">
<a href="../api/modules/_frequency_other_.html">
frequency/other
</a>
</li>
<li class="chapter " data-level="1.5.2.6" data-path="../api/modules/_frequency_time_.html">
<a href="../api/modules/_frequency_time_.html">
frequency/time
</a>
</li>
<li class="chapter " data-level="1.5.2.7" data-path="../api/modules/_frequency_utils_createpubsubobservable_.html">
<a href="../api/modules/_frequency_utils_createpubsubobservable_.html">
frequency/utils/createPubsubObservable
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5.3" >
<span>
RpcObservables
</span>
<ul class="articles">
<li class="chapter " data-level="1.5.3.1" data-path="../api/modules/_rpc_eth_.html">
<a href="../api/modules/_rpc_eth_.html">
rpc/eth
</a>
</li>
<li class="chapter " data-level="1.5.3.2" data-path="../api/modules/_rpc_net_.html">
<a href="../api/modules/_rpc_net_.html">
rpc/net
</a>
</li>
<li class="chapter " data-level="1.5.3.3" data-path="../api/modules/_rpc_other_makecontract_.html">
<a href="../api/modules/_rpc_other_makecontract_.html">
rpc/other/makeContract
</a>
<ul class="articles">
<li class="chapter " data-level="1.5.3.3.1" data-path="../api/interfaces/_rpc_other_makecontract_.makecontract.html">
<a href="../api/interfaces/_rpc_other_makecontract_.makecontract.html">
MakeContract
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5.3.4" data-path="../api/modules/_rpc_other_post_.html">
<a href="../api/modules/_rpc_other_post_.html">
rpc/other/post
</a>
<ul class="articles">
<li class="chapter " data-level="1.5.3.4.1" data-path="../api/interfaces/_rpc_other_post_.postoptions.html">
<a href="../api/interfaces/_rpc_other_post_.postoptions.html">
PostOptions
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5.3.5" data-path="../api/modules/_rpc_parity_.html">
<a href="../api/modules/_rpc_parity_.html">
rpc/parity
</a>
</li>
<li class="chapter " data-level="1.5.3.6" data-path="../api/modules/_rpc_rpc_.html">
<a href="../api/modules/_rpc_rpc_.html">
rpc/rpc
</a>
</li>
<li class="chapter " data-level="1.5.3.7" data-path="../api/modules/_rpc_utils_createrpc_.html">
<a href="../api/modules/_rpc_utils_createrpc_.html">
rpc/utils/createRpc
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5.4" data-path="../api/modules/_types_.html">
<a href="../api/modules/_types_.html">
Types
</a>
<ul class="articles">
<li class="chapter " data-level="1.5.4.1" data-path="../api/interfaces/_types_.frequencyobservable.html">
<a href="../api/interfaces/_types_.frequencyobservable.html">
FrequencyObservable
</a>
</li>
<li class="chapter " data-level="1.5.4.2" data-path="../api/interfaces/_types_.frequencyobservableoptions.html">
<a href="../api/interfaces/_types_.frequencyobservableoptions.html">
FrequencyObservableOptions
</a>
</li>
<li class="chapter " data-level="1.5.4.3" data-path="../api/interfaces/_types_.makecontract.html">
<a href="../api/interfaces/_types_.makecontract.html">
MakeContract
</a>
</li>
<li class="chapter " data-level="1.5.4.4" data-path="../api/interfaces/_types_.metadata.html">
<a href="../api/interfaces/_types_.metadata.html">
Metadata
</a>
</li>
<li class="chapter " data-level="1.5.4.5" data-path="../api/interfaces/_types_.rpcobservable.html">
<a href="../api/interfaces/_types_.rpcobservable.html">
RpcObservable
</a>
</li>
<li class="chapter " data-level="1.5.4.6" data-path="../api/interfaces/_types_.rpcobservableoptions.html">
<a href="../api/interfaces/_types_.rpcobservableoptions.html">
RpcObservableOptions
</a>
</li>
<li class="chapter " data-level="1.5.4.7" data-path="../api/interfaces/_types_.txstatus.html">
<a href="../api/interfaces/_types_.txstatus.html">
TxStatus
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5.5" >
<span>
Utils
</span>
<ul class="articles">
<li class="chapter " data-level="1.5.5.1" data-path="../api/modules/_utils_isloading_.md">
<span>
utils/isLoading
</a>
</li>
<li class="chapter " data-level="1.5.5.2" data-path="../api/modules/_utils_isobservable_.html">
<a href="../api/modules/_utils_isobservable_.html">
utils/isObservable
</a>
</li>
<li class="chapter " data-level="1.5.5.3" data-path="../api/modules/_utils_operators_distinctreplayrefcount_.md">
<span>
utils/operators/distinctReplayRefCount
</a>
</li>
<li class="chapter " data-level="1.5.5.4" data-path="../api/modules/_utils_operators_distinctvalues_.html">
<a href="../api/modules/_utils_operators_distinctvalues_.html">
utils/operators/distinctValues
</a>
</li>
<li class="chapter " data-level="1.5.5.5" data-path="../api/modules/_utils_operators_switchmappromise_.html">
<a href="../api/modules/_utils_operators_switchmappromise_.html">
utils/operators/switchMapPromise
</a>
</li>
<li class="chapter " data-level="1.5.5.6" data-path="../api/modules/_utils_testhelpers_mockapi_.html">
<a href="../api/modules/_utils_testhelpers_mockapi_.html">
utils/testHelpers/mockApi
</a>
</li>
<li class="chapter " data-level="1.5.5.7" data-path="../api/modules/_utils_testhelpers_mockrpc_.html">
<a href="../api/modules/_utils_testhelpers_mockrpc_.html">
utils/testHelpers/mockRpc
</a>
</li>
</ul>
</li>
</ul>
</li>
<li class="divider"></li>
<li>
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
Published with GitBook
</a>
</li>
</ul>
</nav>
</div>
<div class="book-body">
<div class="body-inner">
<div class="book-header" role="navigation">
<!-- Title -->
<h1>
<i class="fa fa-circle-o-notch fa-spin"></i>
<a href=".." >Light Client Development</a>
</h1>
</div>
<div class="page-wrapper" tabindex="-1" role="main">
<div class="page-inner">
<div id="book-search-results">
<div class="search-noresults">
<section class="normal markdown-section">
<h1 id="light-client-development">Light Client Development</h1>
<h2 id="what-is-a-light-client">What is a Light Client</h2>
<p>A Light Client is a special kind of Ethereum node that is, as suggested by its name, light. Concretely, this means:</p>
<ul>
<li>low on resources usage: CPU, memory, storage, I/O operations...</li>
<li>embeddable: in a desktop application, on mobile, within a web app</li>
<li>but still remains trustless</li>
</ul>
<table>
<thead>
<tr>
<th></th>
<th>Full Node</th>
<th>Light Node</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sync time</td>
<td>~days</td>
<td>~seconds</td>
</tr>
<tr>
<td>State size</td>
<td>~gigs</td>
<td>0</td>
</tr>
<tr>
<td>DB size</td>
<td>~tens of gigs</td>
<td>~megs</td>
</tr>
</tbody>
</table>
<p>To learn more about Parity's Light Client, please refer to our wiki: <a href="https://wiki.parity.io/Light-Client" target="_blank">https://wiki.parity.io/Light-Client</a>.</p>
<h2 id="current-dapp-development-patterns">Current Dapp Development Patterns</h2>
<p>If you are a dapp developer, you are probably used to work with a remote full node on the backend, which you connect to with <a href="https://github.com/ethereum/web3.js/" target="_blank"><code>web3.js</code></a>:</p>
<ul>
<li>either via MetaMask: <code>web3js = new Web3(web3.currentProvider);</code>, which internally connects to INFURA.</li>
<li>or connecting to INFURA directly: <code>web3js = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io'));</code>.</li>
</ul>
<p>So all your JSONRPC requests require a network call. As such, some patterns for making these requests start to appear. Let's explore some of them, the use case is we want to constantly show the latest balance of an ERC20 token.</p>
<h3 id="1-example-of-naive-polling-which-doesnt-work-well">1. Example of naive polling, which doesn't work well</h3>
<p>For example, if you want to fetch the latest ERC20 token balance, the following piece of code (which might actually work), doesn't make sense:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> contract = web3.eth.Contract(ABI, <span class="hljs-string">'0x00..ff'</span>);
setInterval(<span class="hljs-keyword">async</span> () => {
<span class="hljs-keyword">const</span> balance = <span class="hljs-keyword">await</span> contract.methods.balanceOf(<span class="hljs-string">'0x00..ff'</span>).call();
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#balance'</span>).textContent = balance;
}, <span class="hljs-number">500</span>);
</code></pre>
<p>We're querying the contract balance every 500ms, so we're making a network request every 500ms. This is too many requests, and moreover, if one network requests takes more than 500ms to resolve, then the displayed result on your dapp could be outdated.</p>
<h3 id="2-smart-polling">2. Smart Polling</h3>
<p>There are of course smarter patterns that we see coming again and again in the dapp development community, which become some basic 101 knowledge between dapp developers.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> contract = web3.eth.Contract(ABI, <span class="hljs-string">'0x00..ff'</span>);
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateBalance</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">const</span> balance = <span class="hljs-keyword">await</span> contract.methods.balanceOf(<span class="hljs-string">'0x00..ff'</span>).call();
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#balance'</span>).textContent = balance;
}
<span class="hljs-keyword">const</span> targetTime = <span class="hljs-number">15</span> * <span class="hljs-number">1000</span>;
setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">const</span> next = <span class="hljs-built_in">Date</span>.now() + targetTime;
updateBalance().then(() => {
setTimeout(update, next - <span class="hljs-built_in">Date</span>.now());
});
});
</code></pre>
<h3 id="3-pubsub">3. Pubsub</h3>
<p>A more intelligent way would be to have a "push" mechanism instead of a "pull" mechanism. With pubsub, we can subscribe to changes on the network. Here, we subscribing to new headers, and updating balance each time we receive a new header.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> contract = web3.eth.Contract(ABI, <span class="hljs-string">'0x00..ff'</span>);
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateBalance</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">const</span> balance = <span class="hljs-keyword">await</span> contract.methods.balanceOf(<span class="hljs-string">'0x00..ff'</span>).call();
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#balance'</span>).textContent = balance;
}
web3.eth.subscribe(<span class="hljs-string">'newBlockHeaders'</span>).on(<span class="hljs-string">'data'</span>, updateBalance);
</code></pre>
<h2 id="paritylightjs-putting-best-patterns-into-a-library"><code>@parity/light.js</code>: Putting best patterns into a library</h2>
<h3 id="which-backend-remote-full-node-vs-light-client">Which backend? Remote Full Node vs Light Client</h3>
<p>Here's a comparison of the advantages each type of node offers.</p>
<table>
<thead>
<tr>
<th>Remote Full Node</th>
<th>Light Client</th>
</tr>
</thead>
<tbody>
<tr>
<td>-</td>
<td>We don't use a centralized backend to fetch data we need.</td>
</tr>
<tr>
<td>-</td>
<td>We verify the data we receive, instead of blindly trusting the backend.</td>
</tr>
<tr>
<td>Much easier to get started with.</td>
<td>-</td>
</tr>
<tr>
<td>Light Client still experimental today.</td>
<td>-</td>
</tr>
</tbody>
</table>
<p>However, from the code's perspective, or from your dapp's UX perspective, both solutions are actually quite similar.</p>
<p>Recall that:</p>
<ul>
<li>the Light Client has no pending blocks or pending transactions.</li>
<li>it actually doesn't have the state at all, so cannot verify and propagate incoming transactions.</li>
<li>mo block bodies (so no transactions), so we cannot fetch transaction data fast.</li>
</ul>
<p>Which means that all JSONRPC calls that require some state, transaction or block data, will require a network call. This is the same as a remote full node.</p>
<p>The (good) development patterns we explored above are therefore also relevant to Light Client development. And that's the goal of <code>@parity/light.js</code>.</p>
<h3 id="the-goal-of-paritylightjs">The goal of @parity/light.js</h3>
<p>The goal of <code>@parity/light.js</code> is basically to regroup all these patterns into a library, so that dapp developers don't need to think about patterns anymore, but just use a simple API provided by the library.</p>
<p>Internally, <code>@parity/light.js</code> uses the Pubsub pattern described above, and mixes it up with Reactive Programming. Jump to the next chapter to find out more.</p>
<h2 id="long-term-vision">Long-term vision</h2>
<p>Our long-term vision at Parity is to embed the Light Client a little bit everywhere: in a browser extension, in a mobile app, in a web app.</p>
<p>In order to do this, we plan to compile Parity to WASM, and embed it into a browser environment. This part is out of scope of this documentation, but if you are interested, please refer to this Github thread: <a href="https://github.com/paritytech/parity-ethereum/issues/7915" target="_blank">https://github.com/paritytech/parity-ethereum/issues/7915</a>.</p>
<p>For example, if we embed it into a browser extension (Chrome Extension, Firefox Add-On), then the Light Client will continuously run in your browser. Similarly to MetaMask, it will inject in authorized websites an Ethereum provider object, which will allow dapps to communicate with the Ethereum network via the extension's Light Client.</p>
<p>If we embed it into a web app directly, it should be as simple as:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// myapp.js</span>
<span class="hljs-keyword">import</span> lightClient <span class="hljs-keyword">from</span> <span class="hljs-string">'@parity/light-client'</span>;
lightClient.run(<span class="hljs-comment">/* Pass in optional flags like "--chain ropsten" etc.*/</span>);
</code></pre>
<p>Which, behind the scenes, will spawn a Light Client in WASM, sync in a matter of seconds, and the web app will seamlessly become a light node on the network, ready to communicate with other peers.</p>
<p>Other places where the Light Client belong are mobile applications or IoT devices.</p>
</section>
</div>
<div class="search-results">
<div class="has-results">
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
<ul class="search-results-list"></ul>
</div>
<div class="no-results">
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
</div>
</div>
</div>
</div>
</div>
</div>
<a href="reactive-programming.html" class="navigation navigation-next navigation-unique" aria-label="Next page: Reactive Programming">
<i class="fa fa-angle-right"></i>
</a>
</div>
<script>
var gitbook = gitbook || [];
gitbook.push(function() {
gitbook.page.hasChanged({"page":{"title":"Light Client Development","level":"1.4.1","depth":2,"next":{"title":"Reactive Programming","level":"1.4.2","depth":2,"path":"concepts/reactive-programming.md","ref":"concepts/reactive-programming.md","articles":[]},"previous":{"title":"Concepts","level":"1.4","depth":1,"ref":"","articles":[{"title":"Light Client Development","level":"1.4.1","depth":2,"path":"concepts/light-client-development.md","ref":"concepts/light-client-development.md","articles":[]},{"title":"Reactive Programming","level":"1.4.2","depth":2,"path":"concepts/reactive-programming.md","ref":"concepts/reactive-programming.md","articles":[]},{"title":"RpcObservables","level":"1.4.3","depth":2,"path":"concepts/rpc-observables.md","ref":"concepts/rpc-observables.md","articles":[]},{"title":"RpcObservables Properties","level":"1.4.4","depth":2,"path":"concepts/rpc-observables-properties.md","ref":"concepts/rpc-observables-properties.md","articles":[]}]},"dir":"ltr"},"config":{"gitbook":"*","theme":"default","variables":{},"plugins":[],"pluginsConfig":{"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"}},"file":{"path":"concepts/light-client-development.md","mtime":"2020-02-10T09:51:45.380Z","type":"markdown"},"gitbook":{"version":"3.2.3","time":"2020-02-10T09:56:25.139Z"},"basePath":"..","book":{"language":""}});
});
</script>
</div>
<script src="../gitbook/gitbook.js"></script>
<script src="../gitbook/theme.js"></script>
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
<script src="../gitbook/gitbook-plugin-search/search.js"></script>
<script src="../gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
<script src="../gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
<script src="../gitbook/gitbook-plugin-sharing/buttons.js"></script>
<script src="../gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
</body>
</html>