chore: move embark site into mono repo
|
@ -18,3 +18,4 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
yarn.lock
|
||||
!/yarn.lock
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"clean:full": "npx npm-run-all clean clean:top",
|
||||
"clean:top": "npx rimraf node_modules",
|
||||
"cwtree": "node scripts/check-working-tree",
|
||||
"deploy:site": "node site/deploy-site",
|
||||
"globalize": "node scripts/globalize",
|
||||
"lint": "lerna run --parallel lint",
|
||||
"package": "lerna run --parallel package",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
public
|
||||
db.json
|
||||
debug.log
|
||||
!/yarn.lock
|
||||
|
|
@ -0,0 +1 @@
|
|||
embark.status.im
|
|
@ -0,0 +1,7 @@
|
|||
Copyright (c) 2013 Tommy Chen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,25 @@
|
|||
# Embark Official Website
|
||||
|
||||
The website for Embark.
|
||||
|
||||
## Getting started
|
||||
|
||||
Install dependencies:
|
||||
|
||||
``` bash
|
||||
$ git clone https://github.com/status-im/embark-site.git
|
||||
$ cd embark-site
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Generate:
|
||||
|
||||
``` bash
|
||||
$ hexo generate
|
||||
```
|
||||
|
||||
Run server:
|
||||
|
||||
``` bash
|
||||
$ hexo server
|
||||
```
|
|
@ -0,0 +1,42 @@
|
|||
title: Embark
|
||||
subtitle: "The all-in-one developer platform for building and deploying decentralized applications."
|
||||
description: "Embark is a simple & powerful framework for decentralized applications"
|
||||
author: Embark
|
||||
language: en
|
||||
timezone: UTC
|
||||
|
||||
url: https://embark.status.im
|
||||
root: /
|
||||
permalink: news/:year/:month/:day/:title/
|
||||
code_dir: downloads/code
|
||||
new_post_name: :year-:month-:day-:title.md # File name of new posts
|
||||
post_asset_folder: true
|
||||
per_page: 0
|
||||
|
||||
theme: embark
|
||||
deploy:
|
||||
type: git
|
||||
repo: git@github.com:hexojs/hexojs.github.io.git
|
||||
|
||||
highlight:
|
||||
enable: true
|
||||
line_number: false
|
||||
|
||||
disqus_shortname: hexojs
|
||||
google_analytics: UA-79146816-4
|
||||
fb_admins: 100000247608790
|
||||
algolia:
|
||||
en:
|
||||
apiKey: 439d8dc2add18007a2f31be4a9c0ed70
|
||||
indexName: embark
|
||||
twitter: EmbarkProject
|
||||
github: embark-framework/embark
|
||||
autoprefixer:
|
||||
enable: true
|
||||
browsers:
|
||||
- 'last 2 versions'
|
||||
node_sass:
|
||||
outputStyle: nested
|
||||
precision: 5
|
||||
sourceComments: false
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
const { execSync } = require('child_process');
|
||||
const process = require('process');
|
||||
const path = require('path');
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
|
||||
const execWithOutput = (cmd) => execSync(cmd, { stdio: 'inherit' });
|
||||
|
||||
const DEPLOY_REPOSITORY = 'https://github.com/embark-framework/embark-site';
|
||||
const DEPLOY_REMOTE = 'embark-site';
|
||||
|
||||
const TMP_DEPLOY_BRANCH = 'embark-site-deploy';
|
||||
const LOCAL_DEPLOY_BRANCH = 'embark-site-deploy-dist';
|
||||
const REMOTE_DEPLOY_BRANCH = 'gh-pages';
|
||||
const MASTER_BRANCH = 'master';
|
||||
|
||||
const SITE_DIR = 'site';
|
||||
const PUBLIC_DIR = 'public';
|
||||
const ROOT_DIR = path.join(__dirname, '..');
|
||||
|
||||
const remote = args['deploy-remote'] || DEPLOY_REMOTE;
|
||||
const remoteDeployBranch = args['deploy-branch'] || REMOTE_DEPLOY_BRANCH;
|
||||
|
||||
function main() {
|
||||
const hasCorrectRemote = execSync(`git remote -v | grep ${remote}`).toString().indexOf(remote) > -1;
|
||||
const hasCorrectRemoteRepo = remote !== DEPLOY_REMOTE ? true : execSync(`git remote -v | grep ${remote}`).toString().indexOf(DEPLOY_REPOSITORY) > -1;
|
||||
|
||||
if (!hasCorrectRemote || !hasCorrectRemoteRepo) {
|
||||
console.log('Please set up the correct remote to deploy the website');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Deploying website...');
|
||||
process.chdir(SITE_DIR);
|
||||
execWithOutput('npx hexo generate');
|
||||
try {
|
||||
execWithOutput('git branch -D embark-site-deploy');
|
||||
} catch (e) {
|
||||
// It's fine if that command errors
|
||||
}
|
||||
execWithOutput(`git checkout -b ${TMP_DEPLOY_BRANCH}`);
|
||||
execWithOutput(`git add -f ${PUBLIC_DIR}`);
|
||||
execWithOutput('git commit -m "chore(*): adding public folder"');
|
||||
process.chdir(ROOT_DIR);
|
||||
execWithOutput(`git subtree split -P ${SITE_DIR}/${PUBLIC_DIR} -b ${LOCAL_DEPLOY_BRANCH}`);
|
||||
execWithOutput(`git push -f ${remote} ${LOCAL_DEPLOY_BRANCH}:${remoteDeployBranch}`);
|
||||
execWithOutput(`git branch -D ${LOCAL_DEPLOY_BRANCH}`);
|
||||
execWithOutput(`git checkout ${MASTER_BRANCH}`);
|
||||
execWithOutput(`git branch -D ${TMP_DEPLOY_BRANCH}`);
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
'use strict';
|
||||
|
||||
var gulp = require('gulp');
|
||||
var gulpIf = require('gulp-if');
|
||||
var gulpRev = require('gulp-rev');
|
||||
var gulpRevCollector = require('gulp-rev-collector');
|
||||
var gulpRevReplace = require('gulp-rev-replace');
|
||||
var gulpUglify = require('gulp-uglify');
|
||||
var gulpUniqueFiles = require('gulp-unique-files');
|
||||
var gulpUseRef = require('gulp-useref');
|
||||
var gulpCleanCSS = require('gulp-clean-css');
|
||||
var gulpResponsive = require('gulp-responsive');
|
||||
var gulpCheerio = require('gulp-cheerio');
|
||||
var del = require('del');
|
||||
var rename = require('rename');
|
||||
|
||||
var dirs = {
|
||||
public: 'public',
|
||||
screenshots: 'public/build/screenshots'
|
||||
};
|
||||
|
||||
gulp.task('useref', ['screenshot'], function() {
|
||||
var assets = gulpUseRef.assets({
|
||||
searchPath: 'public'
|
||||
});
|
||||
|
||||
return gulp.src('public/**/*.html')
|
||||
.pipe(assets)
|
||||
.pipe(gulpUniqueFiles())
|
||||
.pipe(gulpIf('*.css', gulpCleanCSS()))
|
||||
.pipe(gulpIf('*.js', gulpUglify()))
|
||||
.pipe(gulpRev())
|
||||
.pipe(assets.restore())
|
||||
.pipe(gulpUseRef())
|
||||
.pipe(gulpRevReplace({
|
||||
prefix: '/'
|
||||
}))
|
||||
.pipe(gulp.dest('public'));
|
||||
});
|
||||
|
||||
gulp.task('screenshot:clean', function() {
|
||||
return del([dirs.screenshots + '/**/*']);
|
||||
});
|
||||
|
||||
gulp.task('screenshot:rev', ['screenshot:clean'], function() {
|
||||
return gulp.src('public/themes/screenshots/*.png')
|
||||
.pipe(gulpRev())
|
||||
.pipe(gulp.dest(dirs.screenshots))
|
||||
.pipe(gulpRev.manifest())
|
||||
.pipe(gulp.dest(dirs.screenshots));
|
||||
});
|
||||
|
||||
gulp.task('screenshot:revreplace', ['screenshot:rev'], function() {
|
||||
var destDir = '/build/screenshots';
|
||||
|
||||
return gulp.src([dirs.screenshots + '/rev-manifest.json', 'public/themes/index.html'])
|
||||
.pipe(gulpRevCollector({
|
||||
replaceReved: true,
|
||||
dirReplacements: {
|
||||
'/themes/screenshots': destDir
|
||||
}
|
||||
}))
|
||||
.pipe(gulpCheerio(function($, file) {
|
||||
$('img.plugin-screenshot-img.lazyload').each(function() {
|
||||
var img = $(this);
|
||||
var src = img.attr('data-src') || img.attr('data-org');
|
||||
if (!src) return;
|
||||
|
||||
var jpgPath = replaceBackSlash(rename(src, {extname: '.jpg'}));
|
||||
var jpg2xPath = replaceBackSlash(rename(jpgPath, {suffix: '@2x'}));
|
||||
var srcset = [
|
||||
jpgPath,
|
||||
jpg2xPath + ' 2x'
|
||||
].join(', ');
|
||||
|
||||
img.attr('data-src', jpgPath)
|
||||
.attr('data-srcset', srcset)
|
||||
.attr('data-org', src);
|
||||
});
|
||||
}))
|
||||
.pipe(gulp.dest('public/themes'));
|
||||
});
|
||||
|
||||
gulp.task('screenshot:resize', ['screenshot:rev'], function() {
|
||||
return gulp.src(dirs.screenshots + '/*.png')
|
||||
.pipe(gulpResponsive({
|
||||
'*.png': [
|
||||
{
|
||||
width: '50%',
|
||||
rename: {
|
||||
extname: '.jpg'
|
||||
}
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
suffix: '@2x',
|
||||
extname: '.jpg'
|
||||
}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
progressive: true,
|
||||
format: 'jpeg',
|
||||
quality: 70,
|
||||
stats: false
|
||||
}))
|
||||
.pipe(gulp.dest(dirs.screenshots));
|
||||
});
|
||||
|
||||
gulp.task('screenshot', ['screenshot:rev', 'screenshot:resize', 'screenshot:revreplace']);
|
||||
gulp.task('default', ['useref', 'screenshot']);
|
||||
|
||||
function replaceBackSlash(str) {
|
||||
return str.replace(/\\/g, '/');
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "embark-site",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"hexo": {
|
||||
"version": "3.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^0.20.0",
|
||||
"hexo": "3.8.0",
|
||||
"hexo-autoprefixer": "^2.0.0",
|
||||
"hexo-deployer-git": "^0.3.1",
|
||||
"hexo-generator-alias": "^0.1.3",
|
||||
"hexo-generator-archive": "^0.1.4",
|
||||
"hexo-generator-category": "^0.1.3",
|
||||
"hexo-generator-feed": "^1.1.0",
|
||||
"hexo-generator-sitemap": "^1.1.2",
|
||||
"hexo-renderer-jade": "^0.4.1",
|
||||
"hexo-renderer-marked": "^0.2.10",
|
||||
"hexo-renderer-sass": "^0.3.2",
|
||||
"hexo-renderer-stylus": "^0.3.1",
|
||||
"hexo-server": "^0.2.0",
|
||||
"lodash": "^4.5.1",
|
||||
"lunr": "^2.1.2",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^4.3.0",
|
||||
"eslint-config-hexo": "^2.0.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-cheerio": "^0.6.2",
|
||||
"gulp-clean-css": "^3.7.0",
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-responsive": "^2.8.0",
|
||||
"gulp-rev": "^6.0.1",
|
||||
"gulp-rev-collector": "^1.0.2",
|
||||
"gulp-rev-replace": "^0.4.3",
|
||||
"gulp-uglify": "^1.5.3",
|
||||
"gulp-unique-files": "^0.1.2",
|
||||
"gulp-useref": "^2.1.0",
|
||||
"rename": "^1.0.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
var pathFn = require('path');
|
||||
var _ = require('lodash');
|
||||
var cheerio = require('cheerio');
|
||||
var lunr = require('lunr');
|
||||
|
||||
var localizedPath = ['docs', 'api'];
|
||||
|
||||
function startsWith(str, start) {
|
||||
return str.substring(0, start.length) === start;
|
||||
}
|
||||
|
||||
hexo.extend.helper.register('page_nav', function() {
|
||||
var type = this.page.canonical_path.split('/')[0];
|
||||
var sidebar = this.site.data.sidebar[type];
|
||||
var path = pathFn.basename(this.path);
|
||||
var list = {};
|
||||
var prefix = 'sidebar.' + type + '.';
|
||||
|
||||
for (var i in sidebar) {
|
||||
for (var j in sidebar[i]) {
|
||||
list[sidebar[i][j]] = j;
|
||||
}
|
||||
}
|
||||
|
||||
var keys = Object.keys(list);
|
||||
var index = keys.indexOf(path);
|
||||
var result = '';
|
||||
|
||||
if (index > 0) {
|
||||
result += '<a href="' + keys[index - 1] + '" class="article-footer-prev" title="' + this.__(prefix + list[keys[index - 1]]) + '">'
|
||||
+ '<i class="fa fa-chevron-left"></i><span>' + this.__('page.prev') + '</span></a>';
|
||||
}
|
||||
|
||||
if (index < keys.length - 1) {
|
||||
result += '<a href="' + keys[index + 1] + '" class="article-footer-next" title="' + this.__(prefix + list[keys[index + 1]]) + '">'
|
||||
+ '<span>' + this.__('page.next') + '</span><i class="fa fa-chevron-right"></i></a>';
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('doc_sidebar', function(className) {
|
||||
var type = this.page.canonical_path.split('/')[0];
|
||||
var sidebar = this.site.data.sidebar[type];
|
||||
var path = pathFn.basename(this.path);
|
||||
var result = '';
|
||||
var self = this;
|
||||
var prefix = 'sidebar.' + type + '.';
|
||||
|
||||
_.each(sidebar, function(menu, title) {
|
||||
result += '<strong class="' + className + '-title">' + self.__(prefix + title) + '</strong>';
|
||||
|
||||
_.each(menu, function(link, text) {
|
||||
var itemClass = className + '-link';
|
||||
if (link === path) itemClass += ' current';
|
||||
|
||||
result += '<a href="' + link + '" class="' + itemClass + '">' + self.__(prefix + text) + '</a>';
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('header_menu', function(className) {
|
||||
var menu = this.site.data.menu;
|
||||
var result = '';
|
||||
var self = this;
|
||||
var lang = this.page.lang;
|
||||
var isEnglish = lang === 'en';
|
||||
|
||||
_.each(menu, function(path, title) {
|
||||
if (!isEnglish && ~localizedPath.indexOf(title)) path = lang + path;
|
||||
|
||||
result += '<a href="' + self.url_for(path) + '" class="' + className + '-link">' + self.__('menu.' + title) + '</a>';
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('canonical_url', function(lang) {
|
||||
var path = this.page.canonical_path;
|
||||
if (lang && lang !== 'en') path = lang + '/' + path;
|
||||
|
||||
return this.config.url + '/' + path;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('url_for_lang', function(path) {
|
||||
var lang = this.page.lang;
|
||||
var url = this.url_for(path);
|
||||
|
||||
if (lang !== 'en' && url[0] === '/') url = '/' + lang + url;
|
||||
|
||||
return url;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('raw_link', function(path) {
|
||||
return 'https://github.com/hexojs/site/edit/master/source/' + path;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('page_anchor', function(str) {
|
||||
var $ = cheerio.load(str, {decodeEntities: false});
|
||||
var headings = $('h1, h2, h3, h4, h5, h6');
|
||||
|
||||
if (!headings.length) return str;
|
||||
|
||||
headings.each(function() {
|
||||
var id = $(this).attr('id');
|
||||
|
||||
$(this)
|
||||
.addClass('article-heading')
|
||||
.append('<a class="article-anchor" href="#' + id + '" aria-hidden="true"></a>');
|
||||
});
|
||||
|
||||
return $.html();
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('lunr_index', function(data) {
|
||||
var index = lunr(function() {
|
||||
this.field('name', {boost: 10});
|
||||
this.field('tags', {boost: 50});
|
||||
this.field('description');
|
||||
this.ref('id');
|
||||
|
||||
_.sortBy(data, 'name').forEach((item, i) => {
|
||||
this.add(_.assign({ id: i }, item));
|
||||
});
|
||||
});
|
||||
|
||||
return JSON.stringify(index);
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('canonical_path_for_nav', function() {
|
||||
var path = this.page.canonical_path;
|
||||
|
||||
if (startsWith(path, 'docs/') || startsWith(path, 'api/')) {
|
||||
return path;
|
||||
}
|
||||
return '';
|
||||
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('lang_name', function(lang) {
|
||||
var data = this.site.data.languages[lang];
|
||||
return data.name || data;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('disqus_lang', function() {
|
||||
var lang = this.page.lang;
|
||||
var data = this.site.data.languages[lang];
|
||||
|
||||
return data.disqus_lang || lang;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('hexo_version', function() {
|
||||
return this.env.version;
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
hexo.extend.tag.register('note', function(args, content) {
|
||||
var className = args.shift();
|
||||
var header = '';
|
||||
var result = '';
|
||||
|
||||
if (args.length) {
|
||||
header += '<strong class="note-title">' + args.join(' ') + '</strong>';
|
||||
}
|
||||
|
||||
result += '<blockquote class="note ' + className + '">' + header;
|
||||
result += hexo.render.renderSync({text: content, engine: 'markdown'});
|
||||
result += '</blockquote>';
|
||||
|
||||
return result;
|
||||
}, true);
|
|
@ -0,0 +1 @@
|
|||
embark.status.im
|
|
@ -0,0 +1,43 @@
|
|||
iuri_matias:
|
||||
name: 'Iuri Matias'
|
||||
twitter: 'iurimatias'
|
||||
image: 'https://pbs.twimg.com/profile_images/928272512181563392/iDJdvy2k_400x400.jpg'
|
||||
|
||||
pascal_precht:
|
||||
name: 'Pascal Precht'
|
||||
twitter: 'pascalprecht'
|
||||
image: 'https://pbs.twimg.com/profile_images/993785060733194241/p3oAIMDP_400x400.jpg'
|
||||
|
||||
anthony_laibe:
|
||||
name: 'Anthony Laibe'
|
||||
twitter: 'a_laibe'
|
||||
image: 'https://pbs.twimg.com/profile_images/257742900/13168239_400x400.jpg'
|
||||
|
||||
jonathan_rainville:
|
||||
name: 'Jonathan Rainville'
|
||||
twitter: 'ShyolGhul'
|
||||
image: 'https://pbs.twimg.com/profile_images/993873866878570496/-aE4byjj_400x400.jpg'
|
||||
|
||||
andre_medeiros:
|
||||
name: 'Andre Medeiros'
|
||||
twitter: 'superdealloc'
|
||||
image: 'https://pbs.twimg.com/profile_images/965722487735701504/m58KNgWN_400x400.jpg'
|
||||
|
||||
eric_mastro:
|
||||
name: 'Eric Mastro'
|
||||
twitter: 'ericmastro'
|
||||
image: 'https://avatars1.githubusercontent.com/u/5089238?s=460&v=4'
|
||||
|
||||
michael_bradley:
|
||||
name: 'Michael Bradley'
|
||||
image: 'https://avatars3.githubusercontent.com/u/194260?s=460&v=4'
|
||||
|
||||
richard_ramos:
|
||||
name: 'Richard Ramos'
|
||||
twitter: 'richardramos_me'
|
||||
image: 'https://pbs.twimg.com/profile_images/1063160577973866496/aM313uHG_400x400.jpg'
|
||||
|
||||
jonny_zerah:
|
||||
name: 'Jonny Zerah'
|
||||
twitter: 'jonnyzerah'
|
||||
image: 'https://pbs.twimg.com/profile_images/1043774340490248192/gI9aGy17_400x400.jpg'
|
|
@ -0,0 +1,2 @@
|
|||
tutorials: 'Tutorials'
|
||||
announcements: 'Announcements'
|
|
@ -0,0 +1 @@
|
|||
en: English
|
|
@ -0,0 +1,4 @@
|
|||
docs: /docs/
|
||||
plugins: /plugins/
|
||||
chat: /chat/
|
||||
blog: /news/
|
|
@ -0,0 +1,77 @@
|
|||
- name: embark-bamboo
|
||||
description: Plugin to add Bamboo support to embark
|
||||
link: https://www.npmjs.com/package/embark-bamboo
|
||||
thumbnail: bamboo.png
|
||||
tags:
|
||||
- language
|
||||
- smart-contracts
|
||||
- name: embark-solc
|
||||
description: Uses the command line Solidity Compiler for light-speed compile times
|
||||
link: https://www.npmjs.com/package/embark-solc
|
||||
thumbnail: solidity.png
|
||||
tags:
|
||||
- solidity
|
||||
- language
|
||||
- smart-contracts
|
||||
- name: embark-solium
|
||||
description: The official Solium Plugin for the Embark Framework
|
||||
link: https://www.npmjs.com/package/embark-solium
|
||||
thumbnail: solium.png
|
||||
tags:
|
||||
- linter
|
||||
- solidity
|
||||
- solium
|
||||
- name: embark-etherscan-verifier
|
||||
description: Plugin for Embark to flatten and verify contracts on Etherscan
|
||||
link: https://www.npmjs.com/package/embark-etherscan-verifier
|
||||
tags:
|
||||
- solidity
|
||||
- etherscan
|
||||
- smart-contracts
|
||||
- name: embark-status
|
||||
description: Status plugin to interact with embark
|
||||
link: https://www.npmjs.com/package/embark-status-plugin
|
||||
thumbnail: status.png
|
||||
tags:
|
||||
- status
|
||||
- mobile
|
||||
- name: embark-remix
|
||||
description: Plugin to start Remix and Remix directly with Embark
|
||||
link: https://www.npmjs.com/package/embark-remix
|
||||
thumbnail: remix.png
|
||||
tags:
|
||||
- remix
|
||||
- debugger
|
||||
- name: embark-slither
|
||||
description: Plugin to analyse solidity source with Slither
|
||||
link: https://www.npmjs.com/package/embark-slither
|
||||
tags:
|
||||
- solidity
|
||||
- name: embark-snark
|
||||
description: Plugin to compile circom circuits and generate Solidity proof
|
||||
link: https://www.npmjs.com/package/embark-snark
|
||||
tags:
|
||||
- snark
|
||||
- name: embark-fortune
|
||||
description: Displays a fortune message after every build
|
||||
link: https://www.npmjs.com/package/embark-fortune
|
||||
thumbnail: fortune.jpg
|
||||
tags:
|
||||
- fun
|
||||
- name: embark-pug
|
||||
description: Embark plugin to support pug / jade templates
|
||||
link: https://www.npmjs.com/package/embark-pug
|
||||
legacy: true
|
||||
thumbnail: pug.png
|
||||
tags:
|
||||
- pug
|
||||
- jade
|
||||
- templates
|
||||
- name: embark-haml
|
||||
description: Embark plugin to support haml templates
|
||||
link: https://www.npmjs.com/package/embark-haml
|
||||
legacy: true
|
||||
thumbnail: haml.png
|
||||
tags:
|
||||
- haml
|
||||
- templates
|
|
@ -0,0 +1,72 @@
|
|||
docs:
|
||||
getting_started:
|
||||
overview: overview.html
|
||||
installation: installation.html
|
||||
faq: faq.html
|
||||
|
||||
general_usage:
|
||||
creating_project: create_project.html
|
||||
structure: structure.html
|
||||
running_apps: running_apps.html
|
||||
dashboard: dashboard.html
|
||||
using_the_console: using_the_console.html
|
||||
environments: environments.html
|
||||
configuration: configuration.html
|
||||
pipeline_and_webpack: pipeline_and_webpack.html
|
||||
# setting_up_storages: foo.html
|
||||
# uploading_data: foo.html
|
||||
# configuring_whisper: foo.html
|
||||
# working_with_ens: foo.html
|
||||
javascript_usage: javascript_usage.html
|
||||
|
||||
smart_contracts:
|
||||
contracts_configuration: contracts_configuration.html
|
||||
contracts_deployment: contracts_deployment.html
|
||||
contracts_imports: contracts_imports.html
|
||||
contracts_testing: contracts_testing.html
|
||||
contracts_javascript: contracts_javascript.html
|
||||
|
||||
blockchain_node:
|
||||
blockchain_configuration: blockchain_configuration.html
|
||||
blockchain_accounts_configuration: blockchain_accounts_configuration.html
|
||||
|
||||
storage:
|
||||
storage_configuration: storage_configuration.html
|
||||
storage_deployment: storage_deployment.html
|
||||
storage_javascript: storage_javascript.html
|
||||
|
||||
messages:
|
||||
messages_configuration: messages_configuration.html
|
||||
messages_javascript: messages_javascript.html
|
||||
|
||||
naming:
|
||||
naming_configuration: naming_configuration.html
|
||||
naming_javascript: naming_javascript.html
|
||||
|
||||
# embarkjs:
|
||||
# installing_embarkjs: installing_embarkjs.html
|
||||
# smart_contract_objects: smart_contract_objects.html
|
||||
# using_storages: using_storages.html
|
||||
# sending_and_receiving_messages: sending_and_receiving_messages.html
|
||||
# working_with_name_systems: working_with_name_systems.html
|
||||
plugins:
|
||||
installing_a_plugin: installing_plugins.html
|
||||
creating_a_plugin: creating_plugins.html
|
||||
plugin_reference: plugin_reference.html
|
||||
#advanced_topis:
|
||||
#adwebpack: webpack.html
|
||||
cockpit:
|
||||
cockpit_introduction: cockpit_introduction.html
|
||||
cockpit_dashboard: cockpit_dashboard.html
|
||||
cockpit_deployment: cockpit_deployment.html
|
||||
cockpit_explorer: cockpit_explorer.html
|
||||
cockpit_editor: cockpit_editor.html
|
||||
cockpit_debugger: cockpit_debugger.html
|
||||
#cockpit_utils: cockpit_utils.html
|
||||
reference:
|
||||
# console_commands: console_commands.html
|
||||
embark_commands: embark_commands.html
|
||||
miscellaneous:
|
||||
migrating_from_3: migrating_from_3.x.html
|
||||
troubleshooting: troubleshooting.html
|
||||
contributing: contributing.html
|
|
@ -0,0 +1,74 @@
|
|||
- name: Vyper Template
|
||||
description: Template to demonstrate the use of the Vyper contracts
|
||||
install: embark new AppName --template vyper
|
||||
thumbnail: vyper.png
|
||||
link: https://github.com/embark-framework/embark-vyper-template
|
||||
tags:
|
||||
- vyper
|
||||
- contracts
|
||||
|
||||
- name: Embark Demo React Template
|
||||
description: A React Application showcasing Embark's functionality
|
||||
install: embark demo
|
||||
thumbnail: react.png
|
||||
link: https://embark.status.im/docs/create_project.html#Creating-a-Demo-Project
|
||||
tags:
|
||||
- react
|
||||
- contracts
|
||||
- whisper
|
||||
- ipfs
|
||||
|
||||
- name: Typescript Template
|
||||
description: Template with Typescript support pre-configured
|
||||
thumbnail: typescript.png
|
||||
install: embark new AppName --template typescript
|
||||
link: https://github.com/embark-framework/embark-typescript-template
|
||||
tags:
|
||||
- typescript
|
||||
- frontend
|
||||
|
||||
- name: Vue.js Template
|
||||
description: Ready to use Template for using Embark with vue.js
|
||||
thumbnail: vuejs.png
|
||||
install: embark new AppName --template vue
|
||||
link: https://github.com/embark-framework/embark-vue-template
|
||||
tags:
|
||||
- vue.js
|
||||
- frontend
|
||||
|
||||
#- name: Angular Template
|
||||
# description: Ready to use Template for using Embark with angular.js
|
||||
# thumbnail: angular.png
|
||||
# install: embark new AppName --template angular
|
||||
# link: https://github.com/embark-framework/embark-angular-template
|
||||
# tags:
|
||||
# - angular.js
|
||||
# - frontend
|
||||
|
||||
- name: ethvtx Template
|
||||
description: Demo app for ethvtx, an Ethereum-Ready & Framework-Agnostic Redux configuration
|
||||
thumbnail: vortex.png
|
||||
install: embark new AppName --template Horyus/ethvtx_embark
|
||||
link: https://github.com/Horyus/ethvtx_embark
|
||||
tags:
|
||||
- react
|
||||
|
||||
- name: Bamboo Template
|
||||
description: Template to demonstrate use of the Bamboo contracts
|
||||
install: embark new AppName --template bamboo
|
||||
thumbnail: bamboo.png
|
||||
link: https://github.com/embark-framework/embark-bamboo-template
|
||||
tags:
|
||||
- bamboo
|
||||
- contracts
|
||||
|
||||
- name: Solidity Gas Golfing
|
||||
description: Boilerplate and tests for the first Solidity Gas Golfing Contest
|
||||
thumbnail: sggc.png
|
||||
install: embark new AppName --template embark-framework/sggc
|
||||
link: https://github.com/embark-framework/sggc
|
||||
tags:
|
||||
- solidity
|
||||
- tests
|
||||
- fun
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
- name: How to create a Token Factory with Ethereum — Part 1
|
||||
description: Create and Deploy a Token with Ethereum
|
||||
link: /tutorials/token_factory_1.html
|
||||
tags:
|
||||
- token
|
||||
- ethereum
|
||||
- name: How to create a Token Factory with Ethereum — Part 2
|
||||
description: Create a DApp that can deploy Tokens on the fly
|
||||
link: /tutorials/token_factory_2.html
|
||||
tags:
|
||||
- token
|
||||
- ethereum
|
||||
- client-side-deployment
|
||||
- name: How to deploy to a testnet with Infura
|
||||
description: Deploy and interact with your Dapp on a testnet with the use of Infura
|
||||
link: /tutorials/infura_guide.html
|
||||
tags:
|
||||
- ethereum
|
||||
- deployment
|
||||
- testnet
|
|
@ -0,0 +1,6 @@
|
|||
"latest":
|
||||
label: "stable (v4)"
|
||||
url: https://embark.status.im/docs
|
||||
"3.2":
|
||||
label: "v3.2"
|
||||
url: https://5ca4e0fdb29712000adde37f--embark-site-versions.netlify.com/docs/
|
|
@ -0,0 +1,61 @@
|
|||
title: Embark 2.5.0
|
||||
summary: Today we're excited to announce the release of Embark 2.5.0! Read on for what's in it.
|
||||
author: iuri_matias
|
||||
categories:
|
||||
- announcements
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
## To Update to 2.5.0
|
||||
|
||||
Embark's npm package has changed from `embark-framework` to `embark`, this sometimes can create conflicts. To update, first uninstall embark-framework 1 to avoid any conflicts with `npm uninstall -g embark-framework` followed by `npm install -g embark`
|
||||
|
||||
to update from 2.4.2:
|
||||
|
||||
```
|
||||
npm install -g embark@2.5
|
||||
```
|
||||
|
||||
afterwards make sure `embark version` returns `2.5.0`.
|
||||
|
||||
## In this release
|
||||
|
||||
This release updates to the lastest dependencies, fixes a few things and has a lot of work under the hood necessary for future releases.
|
||||
|
||||
## Updates
|
||||
|
||||
* support for geth 1.6.5
|
||||
* updated to use web3.js 0.19.11
|
||||
* updated to use solc 0.4.11
|
||||
|
||||
## Misc Bugfixes and Improvements
|
||||
|
||||
* `embark new` will now prompt for the dapp name if not specified as `embark new <yourDappName>`
|
||||
* embark.js: `ContractName.new()` as been added as an alias for `ContractName.deploy()`
|
||||
* embark.js: a method to easily send ether to a contract has been added: `ContractName.send(value, unit, options)` e.g `ContractName.send(2, "ether", {from: web3.eth.accounts[1]})`
|
||||
* orbit: Fix for orbit to make it work if the blockchain component is disabled
|
||||
* orbit: Use default config for orbit it none is specified in the config file
|
||||
* Demo app now has warning message for incompatible whisper versions
|
||||
* the JSON files of the contracts are now being outputted at dist/contracts/ (experimental)
|
||||
* whisper: Dashboard now displays the whisper version of the node
|
||||
* plugin API: extensions can now also be added as directories within the dapp directory
|
||||
* plugin API: plugins can now register a component to be displayed in the dashboard. e.g:
|
||||
|
||||
```Javascript
|
||||
embark.registerServiceCheck('PluginService', function(cb) {
|
||||
if (someFunctionThatChecksTheService()) {
|
||||
cb({name: "MyServiceName", status: "on"});
|
||||
} else {
|
||||
cb({name: "MyServiceName", status: "off"});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Thank you
|
||||
|
||||
A big thanks to all that contributed to this release including [Nathan Hernandez](https://github.com/nathanph), [Antonio Tenorio-Fornés](https://github.com/atfornes), [Jon Johnson](https://github.com/jonjonsonjr), Andy Nogueira, [roo2](https://github.com/roo2), [Carl Mönnig](https://github.com/carlmon), [Michael Yeates](https://github.com/michaeljyeates), [Todd Baur](https://github.com/toadkicker), [黄俊钦](https://github.com/imtypist), [Ramiro Moreira](https://github.com/RamiroMoreira), [gregg dourgarian](https://github.com/greggdourgarian)
|
||||
|
||||
## Chatroom
|
||||
|
||||
To discuss about Embark or Dapp development, please [join us at the gitter channel](https://gitter.im/iurimatias/embark-framework)
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
title: Embark 2.6.0 - web3.js 1.0, any version of web3.js & solc. Whisper 5 & much more
|
||||
author: iuri_matias
|
||||
categories:
|
||||
- announcements
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
## To Update to 2.6.0
|
||||
|
||||
Embark's npm package has changed from `embark-framework` to `embark`, this sometimes can create conflicts. To update, first uninstall embark-framework 1 to avoid any conflicts with `npm uninstall -g embark-framework` followed by `npm install -g embark`
|
||||
|
||||
to update from 2.5.2:
|
||||
|
||||
```
|
||||
npm install -g embark@2.6
|
||||
```
|
||||
|
||||
afterwards make sure `embark version` returns `2.6.0`.
|
||||
|
||||
## In this release
|
||||
|
||||
You no longer need to wait for new releases of embark when a new version of web3.js or solc comes out as this can be now configured. Embark will take care of downloading and using the new versions. You can specify a list of nodes the dapp should attempt to connect to (instead of being limited 1). Whisper 5 is now supported.
|
||||
|
||||
## Support for web3.js 1.0 and (nearly) ANY web3.js version
|
||||
|
||||
Embark now supports web3.js 1.0 by default, however you can now also specify exactly which version of web3.js you want to use so you can still use 0.19.0 or newer versions of 1.0.
|
||||
|
||||
in config/contracts.json
|
||||
|
||||
```
|
||||
{
|
||||
"default": {
|
||||
....
|
||||
"versions": {
|
||||
"web3.js": "1.0.0-beta"
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
If ,for example, you wish to use 0.19.0 you can specify it in the config as `"web3.js": "0.19.0"`
|
||||
|
||||
## Support for ANY solc version
|
||||
|
||||
You can also configure the solc compiler you wish to use and it should work, so long that solc release does not contain breaking API changes.
|
||||
|
||||
`config/contracts.json`
|
||||
|
||||
```
|
||||
{
|
||||
"default": {
|
||||
....
|
||||
"versions": {
|
||||
"solc": "0.4.17"
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Specify nodes DApp should attempt to connect to
|
||||
|
||||
You can specify which nodes your dapp should try to connect in each enviroment. "$WEB3" is a special keyword to specify the existing web3 object.
|
||||
The following config would attempt to use the existing web3 object and if unavailable attempt to connect to localhost:8545
|
||||
|
||||
`config/contracts.json`
|
||||
|
||||
```
|
||||
{
|
||||
"development": {
|
||||
...
|
||||
"dappConnection": [
|
||||
"$WEB3",
|
||||
"http://localhost:8545"
|
||||
],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Specify node to deploy to
|
||||
|
||||
Before Embark would assume this would be the same as the one configured in blockchain.json which could lead to some ackward configs for some devs, this has now been changed so you can specify it in the contracts config.
|
||||
|
||||
`config/contracts.json`
|
||||
|
||||
```
|
||||
{
|
||||
"development": {
|
||||
...
|
||||
"deployment": {
|
||||
"host": "localhost",
|
||||
"port": 8545,
|
||||
"type": "rpc"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Specify node to connect whisper to
|
||||
|
||||
`config/communication.json`
|
||||
```Javascript
|
||||
{
|
||||
"default": {
|
||||
"enabled": true,
|
||||
"provider": "whisper",
|
||||
"available_providers": ["whisper", "orbit"],
|
||||
"connection": {
|
||||
"host": "localhost",
|
||||
"port": 8546,
|
||||
"type": "ws"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Specify url to get assets
|
||||
|
||||
You can specify for each environment what IPFS node to get the assets from
|
||||
|
||||
`config/storage.json`
|
||||
|
||||
```Javascript
|
||||
{
|
||||
...
|
||||
"development": {
|
||||
....
|
||||
"getUrl": "http://localhost:8080/ipfs/"
|
||||
},
|
||||
...
|
||||
"livenet": {
|
||||
....
|
||||
"getUrl": "https://gateway.ipfs.io/ipfs/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin API changes
|
||||
|
||||
![plugin](http://icons.iconarchive.com/icons/elegantthemes/beautiful-flat/128/plugin-icon.png)
|
||||
|
||||
The following events are deprecated: abi-vanila, abi, abi-contracts-vanila, abi-vanila-deployment and have been renamed to code-vanila, code, code-contracts-vanila, code-vanila-deployment
|
||||
|
||||
plugins that use these events will get deprecation warnings, the deprecated events will be removed in 2.7.0
|
||||
|
||||
|
||||
### New Blockchain options
|
||||
|
||||
![geth](https://dappsforbeginners.files.wordpress.com/2015/02/ethereum-logo.jpg?w=200)
|
||||
|
||||
|
||||
The following fields are now available at `config/blockchain.json` to enhance `embark blockchain`:
|
||||
|
||||
* "wsHost" - to specify the websocket host (default: localhost)
|
||||
* "wsPort" - to specify the websocket port (default: 8546)
|
||||
* "wsOrigins"- to specify the allowed origin of the websocket requests (default: FALSE), must be specified to something like http://localhost:8000 for the websocket connection to work.
|
||||
* "wsApi" - to specify the apis available through websockets (default: ['eth', 'web3', 'net', 'shh'])
|
||||
|
||||
### Misc Bugfixes and Improvements
|
||||
|
||||
![bug fixes](http://i.imgur.com/L1r6Ac5.png)
|
||||
|
||||
* tests no longer need the requires and initialization and can be run directly with embark. however you can still use these requires to run it yourself with mocha or your own preferred test framework
|
||||
* embark and mocha are no longer dependencies in the created dapp
|
||||
* you can specify a test file with `embark test <filename>`
|
||||
* tests no longer need testrpc to be installed first
|
||||
* `EmbarkJS.isNewWeb3()` to detect if web3 1.0 is available
|
||||
* demo app updated to use web3.js 1.0 and solc 0.4.17
|
||||
* warn user when websocket or http CORS is not set
|
||||
* tolerate solc compiler warnings, which could cause a crash sometimes
|
||||
|
||||
|
||||
### Thank you
|
||||
|
||||
A big thanks to all that contributed to this release including [Todd Baur](https://github.com/toadkicker) and Jacob Beauchamp.
|
||||
|
||||
### Chatroom
|
||||
|
||||
To discuss about Embark or Dapp development, please [join us at the gitter channel](https://gitter.im/iurimatias/embark-framework)
|
||||
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
title: Embark by Status 3.0
|
||||
author: iuri_matias
|
||||
summary: "We're happy to announce that Embark 3.0 has been released! Read on for what's inside!"
|
||||
categories:
|
||||
- announcements
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
Embark is now part of [Status](https://status.im/) and we are happy to announce Embark 3.0 by Status!
|
||||
|
||||
## New website and Documentation
|
||||
|
||||
Embark has a new website and up to date documentation which can be found at https://embark.status.im/docs/
|
||||
|
||||
## More Smart Contract Languages
|
||||
|
||||
Besides Solidity, Embark now also supports [Vyper](https://github.com/ethereum/vyper/) out of the box, as well as [Bamboo](https://github.com/pirapira/bamboo) through an embark [plugin](https://github.com/embark-framework/embark-bamboo)
|
||||
You can use these languages side by side, and take advantage of Embark's features such as contract testing just like you would with Solidity.
|
||||
|
||||
## DApp Imports
|
||||
|
||||
From the dapp side, contracts and libs like EmbarkJS can be implicitly imported, for e.g to import a contract:
|
||||
|
||||
```Javascript
|
||||
import SimpleStorage from 'Embark/contracts/SimpleStorage'
|
||||
```
|
||||
|
||||
EmbarkJS:
|
||||
|
||||
```Javascript
|
||||
import EmbarkJS from 'Embark/EmbarkJS'
|
||||
```
|
||||
|
||||
Or a initialized web3 instances (with the config of `config/contracts.json`)
|
||||
|
||||
```Javascript
|
||||
import web3 from 'Embark/web3'
|
||||
```
|
||||
|
||||
The typical ES6 imports will also simply work. You can even import directly css files inside js files:
|
||||
|
||||
```Javascript
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from 'react-bootstrap';
|
||||
|
||||
import './dapp.css';
|
||||
```
|
||||
|
||||
## Friendlier torwards contracts-only projects
|
||||
|
||||
Although Embark is focused on DApps, it can perfectly be used for projects targeting only smart contracts and no other components.
|
||||
|
||||
There is a now a template to create a simple project with all the components disabled except smart contracts:
|
||||
|
||||
`embark new AppName --simple`
|
||||
|
||||
You can also fine tune this in embark.json by specifying the config of each component or setting it to false if you don't want it.
|
||||
|
||||
```JSON
|
||||
...
|
||||
"config": {
|
||||
"contracts": "contracts.json",
|
||||
"blockchain": false,
|
||||
"storage": false,
|
||||
"communication": false,
|
||||
"webserver": false
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
## Embark Graph
|
||||
|
||||
The command `embark graph` will generate a ER graph of the dapp contracts. This takes into account not just the inheritance but also the relationships specified in the configuration.
|
||||
|
||||
## Config contracts from URIs
|
||||
|
||||
Embark now supports referencing directly URIs including http, git, github, or directly files contained in other directories than the ones specified in embark.json
|
||||
|
||||
Embark is smart enough to take care of the dependencies of the resources and present them in a consistent manner to the compiler, it just works!
|
||||
|
||||
```JSON
|
||||
{
|
||||
"development": {
|
||||
"contracts": {
|
||||
"ERC725": {
|
||||
"file": "git://github.com/status/contracts/contracts/identity/ERC725.sol#develop"
|
||||
},
|
||||
"ERC725": {
|
||||
"file": "github.com/status/contracts/contracts/identity/ERC725.sol"
|
||||
},
|
||||
"Ownable": {
|
||||
"file": "https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol"
|
||||
},
|
||||
"SimpleStorage": {
|
||||
"file": "./some_folder/simple_storage.sol"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Importing contracts from URIs directly in Solidity
|
||||
|
||||
You can also import the same URIs directly in solidity which is quite useful for interfaces, e.g:
|
||||
|
||||
```Javascript
|
||||
import "git://github.com/status/contracts/contracts/identity/ERC725.sol#develop";
|
||||
import "github.com/status/contracts/contracts/identity/ERC725.sol";
|
||||
import "https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol"
|
||||
|
||||
contract MyContract is Ownable {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Contracts from npm packages
|
||||
|
||||
You can now install npm packages that contain contracts (e.g `npm install --save openzeppelin-solidity`) and refer them to them in the contracts.json file:
|
||||
|
||||
```Javascript
|
||||
{
|
||||
"development": {
|
||||
"contracts": {
|
||||
"ERC20": {
|
||||
file: "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
or even import them directly in solidity without the need for the config:
|
||||
|
||||
```Solidity
|
||||
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
|
||||
|
||||
contract MyContract is Ownable {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Embark Demo App
|
||||
|
||||
The demo app has been updated to reflect the new structure. It also now uses ReactJS which provides a good example on how to use React with Embark.
|
||||
|
||||
## Web3.js 1.0 by default
|
||||
|
||||
Embark now uses web3.js 1.0 in all layers, including in the console and in contracts testing.
|
||||
|
||||
|
||||
## More contract deploy configs
|
||||
|
||||
A new config called `afterDeploy` is available and it can be used to specify actions to run after all contracts have been deployed.
|
||||
It's possible to also specify the specific account to deploy from using the directive `from` or `fromIndex`
|
||||
|
||||
## Versions Configuration
|
||||
|
||||
The versions config has been moved to embark.json, the download mechanism has also been fastly improved under the hood:
|
||||
|
||||
```
|
||||
...
|
||||
"versions": {
|
||||
"web3": "1.0.0-beta",
|
||||
"solc": "0.4.23",
|
||||
"ipfs-api": "17.2.4"
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Test Improvements
|
||||
|
||||
In the tests you can now specify a mnemonic:
|
||||
|
||||
```Javascript
|
||||
config({
|
||||
mnemonic: "labor ability deny divide mountain buddy home client type shallow outer pen"
|
||||
})
|
||||
````
|
||||
|
||||
It's also possible to specify a node, in case you don't want to run in the internal vm:
|
||||
|
||||
```Javascript
|
||||
config({
|
||||
node: "http://localhost:8545"
|
||||
})
|
||||
````
|
||||
|
||||
## Swarm support
|
||||
|
||||
Swarm is now completely integrated on-par with IPFS. You can use interact with Swarm on the dapp side, as well as upload your dapp to Swarm.Swarm
|
||||
|
||||
## Misc Bugfixes and Improvements
|
||||
|
||||
For a complete list please refer to the [release notes in github](https://github.com/embark-framework/embark/releases/tag/3.0.0)
|
||||
|
||||
## Chatroom
|
||||
|
||||
To discuss about Embark or Dapp development, please [join us at the gitter channel](https://gitter.im/embark-framework/Lobby)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
title: Embark by Status 3.1
|
||||
author: iuri_matias
|
||||
summary: "In this article we're going to explore what the 3.1 release of Embark has to offer!"
|
||||
categories:
|
||||
- announcements
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
More info can be found in the [medium post](https://blog.status.im/embark-3-1-planet-express-60493ca0ad79)
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
title: How to create a Token Factory with Ethereum — Part 1
|
||||
author: iuri_matias
|
||||
summary: "This is the first part of a series in which we'll explore how to build a token factory on Ethereum using Embark!"
|
||||
categories:
|
||||
- tutorials
|
||||
alias:
|
||||
- "tutorials/token_factory_1.html"
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
In this tutorial series we’ll create a Token Factory using Ethereum. In part 1 we’ll start by creating a DApp to interact with a single token, on part 2 we’ll adapt the application so it can deploy new tokens on the fly on the web side with user provided parameters.
|
||||
|
||||
A Token is typically a unit used to represent a medium of exchange for some service or utility. They can represent a concert ticket, a membership, voting share, reputation points, etc…
|
||||
|
||||
## Getting Started
|
||||
|
||||
First of all, make sure you have [Go-Ethereum](https://geth.ethereum.org/) and Embark installed.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
$ npm -g install embark
|
||||
{% endcode_block %}
|
||||
|
||||
Now, let’s create a new dapp
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
$ embark new TokenFactory
|
||||
{% endcode_block %}
|
||||
|
||||
|
||||
This will create a directory called TokenFactory, cd to it and run:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
$ embark run
|
||||
{% endcode_block %}
|
||||
|
||||
In another console, in the same directory, run:
|
||||
|
||||
You should see something like this:
|
||||
|
||||
![Dashboard Code](/assets/images/token_factory_1/dashboard.png)
|
||||
|
||||
To exit the dashboard you can type 'exit' in the console or press CTRL+C.
|
||||
|
||||
{% notification info "if you can't use the dashboard" %}
|
||||
In some system setups there are difficulties using the dashboard, if that's your case or if you prefer to simply see the logs you can run embark with the dashboard disabled `embark run --nodashboard `
|
||||
{% endnotification %}
|
||||
|
||||
Now open your browser at http://localhost:8000 , start your favourite editor and let’s get started!
|
||||
|
||||
## Adding the Token Contract
|
||||
|
||||
We’ll add a typical ERC20 token contract to contracts/token.sol
|
||||
|
||||
*warning: this contract is for educational purposes only, do not use it in production unless you know what you are doing*
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
pragma solidity ^0.4.23;
|
||||
|
||||
contract Token {
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint value);
|
||||
event Approval(address indexed owner, address indexed spender, uint value);
|
||||
|
||||
mapping(address => uint) _balances;
|
||||
mapping(address => mapping( address => uint )) _approvals;
|
||||
uint public _supply;
|
||||
|
||||
constructor(uint initial_balance) public {
|
||||
_balances[msg.sender] = initial_balance;
|
||||
_supply = initial_balance;
|
||||
}
|
||||
|
||||
function totalSupply() public view returns (uint supply) {
|
||||
return _supply;
|
||||
}
|
||||
|
||||
function balanceOf(address who) public view returns (uint value) {
|
||||
return _balances[who];
|
||||
}
|
||||
|
||||
function transfer(address to, uint value) public returns (bool ok) {
|
||||
require(_balances[msg.sender] > value);
|
||||
require(safeToAdd(_balances[to], value));
|
||||
_balances[msg.sender] -= value;
|
||||
_balances[to] += value;
|
||||
emit Transfer(msg.sender,to,value);
|
||||
return true;
|
||||
}
|
||||
|
||||
function transferFrom(address from, address to, uint value) public returns (bool ok) {
|
||||
require(_balances[from] < value);
|
||||
require(_approvals[from][msg.sender] < value);
|
||||
require(safeToAdd(_balances[to], value));
|
||||
_approvals[from][msg.sender] -= value;
|
||||
_balances[from] -= value;
|
||||
_balances[to] += value;
|
||||
emit Transfer(from, to, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
function approve(address spender, uint value) public returns (bool ok) {
|
||||
_approvals[msg.sender][spender] = value;
|
||||
emit Approval(msg.sender, spender, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
function allowance(address owner, address spender) public view returns (uint _allowance) {
|
||||
return _approvals[owner][spender];
|
||||
}
|
||||
|
||||
function safeToAdd(uint a, uint b) internal pure returns (bool) {
|
||||
return (a + b >= a);
|
||||
}
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
Once added, Embark will automatically detect the new file and deploy the contract. However we quickly notice a problem, in Embark’s we see:
|
||||
|
||||
![Console](/assets/images/token_factory_1/console_1.png)
|
||||
|
||||
We haven't supplied any parameters to the contract and embark complains because the contract constructor takes a *initial_balance* parameter which we haven’t specified:
|
||||
|
||||
```
|
||||
constructor(uint initial_balance) public {
|
||||
_balances[msg.sender] = initial_balance;
|
||||
_supply = initial_balance;
|
||||
}
|
||||
```
|
||||
|
||||
Let’s rectify this by specifying the *initial_balance* value in `config/contracts.js`
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
module.exports = {
|
||||
default: {
|
||||
// .....
|
||||
gas: "auto",
|
||||
contracts: {
|
||||
<mark id="code-3" class="highlight-inline">
|
||||
Token: {
|
||||
args: {
|
||||
initial_balance: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
// .....
|
||||
}
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
Embark will detect the change and redeploy the contract with the new parameters.
|
||||
|
||||
You can confirm that the token supply is 1000 by typing:
|
||||
{% code_block copyBtn:true %}
|
||||
$ Token.methods._supply().call(console.log)
|
||||
{% endcode_block %}
|
||||
|
||||
![Console](/assets/images/token_factory_1/console_2.png)
|
||||
|
||||
## Creating the UI
|
||||
|
||||
For the sake of brevity, we wouldn’t implement every single functionality in the contract. However, we’ll implement two important features: Checking balance of an address and Transferring Tokens from one address to another.
|
||||
|
||||
## Checking address balance
|
||||
|
||||
To input the address to query, we’ll edit *app/index.html* and add a simple form.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
<html>
|
||||
<head>
|
||||
<title>Embark</title>
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<script src="js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="queryBalance">
|
||||
<h3>Query Balance</h3>
|
||||
<input placeholder="enter account address: e.g 0x123" />
|
||||
<button>Query</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endcode_block %}
|
||||
|
||||
**Adding jQuery**
|
||||
|
||||
To simplify the code a bit in this tutorial, we’ll add the jQuery library to our DApp.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
$ npm install jquery@3 --save
|
||||
{% endcode_block %}
|
||||
|
||||
Now edit the file *app/js/index.js* and add:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
import $ from 'jquery';
|
||||
{% endcode_block %}
|
||||
|
||||
**Setting the default address**
|
||||
|
||||
Let’s add to the input field field our own address as the default text so we can easily query our own balance. In the file *app/js/index.js* add:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
import $ from 'jquery';
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
|
||||
$(document).ready(function() {
|
||||
EmbarkJS.onReady((error) => {
|
||||
if (error) {
|
||||
console.error('Error while connecting to web3', error);
|
||||
return;
|
||||
}
|
||||
web3.eth.getAccounts(function(err, accounts) {
|
||||
$('#queryBalance input').val(accounts[0]);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
{% endcode_block %}
|
||||
|
||||
This will get the address of the first account and set it as the default text in the input form.
|
||||
|
||||
`EmbarkJS.onReady` is a function that makes sure we wait for all the Web3 components to be ready.
|
||||
|
||||
**Querying Balance**
|
||||
|
||||
To query the balance, we can see the contract method signature to do this is:
|
||||
|
||||
```
|
||||
function balanceOf( address who ) constant returns (uint value) {
|
||||
return _balances[who];
|
||||
}
|
||||
```
|
||||
|
||||
This method will be available in the JS code automatically as a promise, like:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
import Token from 'Embark/contracts/Token';
|
||||
|
||||
Token.methods.balanceOf(address).call().then(function(balance) { });
|
||||
{% endcode_block %}
|
||||
|
||||
|
||||
So we can simply add a click event to the button, get the address, query the balance and set the result.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
import $ from 'jquery';
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import Token from 'Embark/contracts/Token';
|
||||
|
||||
$(document).ready(function() {
|
||||
EmbarkJS.onReady((error) => {
|
||||
if (error) {
|
||||
console.error('Error while connecting to web3', error);
|
||||
return;
|
||||
}
|
||||
web3.eth.getAccounts(function(err, accounts) {
|
||||
$('#queryBalance input').val(accounts[0]);
|
||||
});
|
||||
$('#queryBalance button').click(function() {
|
||||
var address = $('#queryBalance input').val();
|
||||
Token.methods.balanceOf(address).call().then(function(balance) {
|
||||
$('#queryBalance .result').html(balance);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
{% endcode_block %}
|
||||
|
||||
|
||||
![Screenshot](/assets/images/token_factory_1/page_1.png)
|
||||
|
||||
Now go to http://localhost:8000 and click on the Query button, it will return 1000 as expected for our address.
|
||||
|
||||
## Transferring Tokens
|
||||
|
||||
Now let’s implement transferring tokens!
|
||||
|
||||
Now checking the contract, this is the method for transferring tokens:
|
||||
|
||||
```
|
||||
function transfer( address to, uint value) returns (bool ok)
|
||||
```
|
||||
|
||||
The method will take two parameters, an address and a value. Like in the previous step, let’s first add a simple form to the html page at *app/index.html*:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
<html>
|
||||
<head>
|
||||
<title>Embark</title>
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<script src="js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Welcome to Embark!</h3>
|
||||
<p>See the <a href="https://github.com/iurimatias/embark-framework/wiki">Wiki</a> to see what you can do with Embark!</p>
|
||||
|
||||
<div id="queryBalance">
|
||||
<h3>Query Balance</h3>
|
||||
<input placeholder="enter account address: e.g 0x123" />
|
||||
<button>Query</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
<div id="transfer">
|
||||
<h3>Transfer Tokens</h3>
|
||||
<input class="address" placeholder="enter account address: e.g 0x123" />
|
||||
<input class="num" placeholder="enter amount to transfer" />
|
||||
<button>Transfer</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endcode_block %}
|
||||
|
||||
Then we will add the code to take the address and number of tokens from the inputs and call the contracts transfer method to *app/js/index.js*:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
import $ from 'jquery';
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import Token from 'Embark/contracts/Token';
|
||||
|
||||
$(document).ready(function() {
|
||||
EmbarkJS.onReady((error) => {
|
||||
if (error) {
|
||||
console.error('Error while connecting to web3', error);
|
||||
return;
|
||||
}
|
||||
web3.eth.getAccounts(function(err, accounts) {
|
||||
$('#queryBalance input').val(accounts[0]);
|
||||
});
|
||||
$('#queryBalance button').click(function() {
|
||||
var address = $('#queryBalance input').val();
|
||||
Token.methods.balanceOf(address).call().then(function(balance) {
|
||||
$('#queryBalance .result').html(balance);
|
||||
});
|
||||
});
|
||||
$('#transfer button').click(function() {
|
||||
var address = $('#transfer .address').val();
|
||||
var num = $('#transfer .num').val();
|
||||
|
||||
Token.methods.transfer(address, num).send().then(function() {
|
||||
$('#transfer .result').html('Done!');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
{% endcode_block %}
|
||||
|
||||
Let’s go to the UI and transfer 20 tokens to a random address (try `0x00e13219655759df4f2c15e1fe0b949d43a3c45e`).
|
||||
After clicking Transfer you should see the text ‘Done!’ when the transfer takes effect.
|
||||
|
||||
![Screenshot](/assets/images/token_factory_1/page_2.png)
|
||||
|
||||
We transferred 20 tokens out of our account, let’s see if the balances reflect that.
|
||||
|
||||
![Screenshot](/assets/images/token_factory_1/page_3.png)
|
||||
|
||||
![Screenshot](/assets/images/token_factory_1/page_4.png)
|
||||
|
||||
You can even see in the Console a receipt of the transaction:
|
||||
|
||||
![Screenshot](/assets/images/token_factory_1/page_5.png)
|
||||
|
||||
|
||||
## On to Part 2
|
||||
|
||||
In this tutorial we deployed and interacted with single Token. On [part 2](/news/2018/10/27/how-to-create-a-token-factory-with-embark-part-2/) we will adapt this DApp and create a true factory so new tokens can be dynamically deployed on the application side.
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
title: How to create a Token Factory with Ethereum — Part 2
|
||||
author: iuri_matias
|
||||
summary: "In this second part, we'll continue where we left off in part one, on building a token factory with Embark and focus on how to deploy new tokens."
|
||||
categories:
|
||||
- tutorials
|
||||
alias:
|
||||
- "tutorials/token_factory_2.html"
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
In [part 1](/news/2018/09/27/how-to-create-a-token-factory-with-embark-part-1/) we deployed and interacted with a single Token. In this article we will continue by adapting the previous DApp to create a true factory so new tokens can be dynamically deployed on the application side.
|
||||
|
||||
A Token is typically a unit used to represent a medium of exchange for some service or utility. They can represent a concert ticket, a membership, voting share, reputation points, etc…
|
||||
|
||||
## Getting Started
|
||||
|
||||
For the second part of the tutorial, Embark 3.0 or higher is required.
|
||||
|
||||
If you are using an older version you can update with:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
$ npm install -g embark@3
|
||||
{% endcode_block %}
|
||||
|
||||
Afterwards make sure that `embark version` returns 3.0 then restart embark with `embark run`
|
||||
|
||||
## Generalizing Token Interaction
|
||||
|
||||
We’ll start by generalizing the previous UI so we can input the address of a ERC20 Token and interact with it.
|
||||
|
||||
First, we’ll add a simple form to *app/index.html* to get address of the token we wish to interact with.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
<html>
|
||||
<head>
|
||||
<title>Embark</title>
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<script src="js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Welcome to Embark!</h3>
|
||||
<p>See the <a href="https://github.com/iurimatias/embark-framework/wiki">Wiki</a> to see what you can do with Embark!</p>
|
||||
<div id="useToken">
|
||||
<h3>Token Address</h3>
|
||||
<input placeholder="enter token address" />
|
||||
<button>Use this Token</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
<div id="queryBalance">
|
||||
<h3>Query Balance</h3>
|
||||
<input placeholder="enter account address: e.g 0x123" />
|
||||
<button>Query</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
|
||||
<div id="transfer">
|
||||
<h3>Transfer Tokens</h3>
|
||||
<input class="address" placeholder="enter account address: e.g 0x123" />
|
||||
<input class="num" placeholder="enter amount to transfer" />
|
||||
<button>Transfer</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{% endcode_block %}
|
||||
|
||||
In *app/js/index.js* we’ll get the address given in the input, initialize a new contract object for that address and the Token ABI, and then assign it to a variable. We’ll also update the rest of code to use *currentToken* instead of *Token*. This way the existing code will work with the token we will be loading.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import $ from 'jquery';
|
||||
import Token from 'Embark/contracts/Token';
|
||||
|
||||
let currentToken;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#useToken button").click(function() {
|
||||
var address = $('#useToken input').val();
|
||||
currentToken = new EmbarkJS.Contract({
|
||||
abi: Token.options.jsonInterface,
|
||||
address: address
|
||||
});
|
||||
});
|
||||
web3.eth.getAccounts(function(err, accounts) {
|
||||
$('#queryBalance input').val(accounts[0]);
|
||||
});
|
||||
|
||||
$('#queryBalance button').click(function() {
|
||||
var address = $('#queryBalance input').val();
|
||||
currentToken.methods.balanceOf(address).call().then(function(balance) {
|
||||
$('#queryBalance .result').html(balance.toString());
|
||||
});
|
||||
});
|
||||
|
||||
$('#transfer button').click(function() {
|
||||
var address = $('#transfer .address').val();
|
||||
var num = $('#transfer .num').val();
|
||||
currentToken.methods.transfer(address, num).send().then(function() {
|
||||
$('#transfer .result').html('Done!');
|
||||
});;
|
||||
});
|
||||
|
||||
});
|
||||
{% endcode_block %}
|
||||
|
||||
Now you can input the address of an existing token in chain, and interact with it. For instance, checking the embark dashboard.
|
||||
|
||||
![Console](/assets/images/token_factory_2/console_1.png)
|
||||
|
||||
I can see the address of the deployed token in my case is *0x0703da89fc6c3ff20b8787a23d3340b41258dba7*. Copy paste your equivalent address into the UI.
|
||||
|
||||
{% notification info 'Copying the address' %}
|
||||
*There are several ways to copy the address, in most systems pressing the ALT key while dragging with the mouse will enable text selection in the console, followed by CMD+C or right-click->copy.*
|
||||
{% endnotification %}
|
||||
|
||||
![Screenshot](/assets/images/token_factory_2/page_1.png)
|
||||
|
||||
After copying the address, click “Use this Token’, and let’s see the balance.
|
||||
|
||||
![Screenshot](/assets/images/token_factory_2/page_2.png)
|
||||
|
||||
It’s *980* as expected (*1000* was the initial supply as configured in *config/contracts.json* and *20* was transferred out in [part 1](/news/2018/09/27/how-to-create-a-token-factory-with-embark-part-1/)
|
||||
|
||||
## Deploy New Tokens on the fly
|
||||
|
||||
Now that we have an UI to interact with an existing Token given its address, we’ll add functionality to deploy tokens on the fly, each with their own initial supply.
|
||||
|
||||
First we’ll add a simple form to *app/index.html* to get the desired supply of the new token to deploy.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
<html>
|
||||
<head>
|
||||
<title>Embark</title>
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<script src="js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Welcome to Embark!</h3>
|
||||
<p>See the <a href="https://github.com/iurimatias/embark-framework/wiki">Wiki</a> to see what you can do with Embark!</p>
|
||||
<div id="deployToken">
|
||||
<h3>Deploy new Token</h3>
|
||||
<input placeholder="enter token supply" />
|
||||
<button>Deploy</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
<div id="useToken">
|
||||
<h3>Token Address</h3>
|
||||
<input placeholder="enter token address" />
|
||||
<button>Use this Token</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
|
||||
<div id="queryBalance">
|
||||
<h3>Query Balance</h3>
|
||||
<input placeholder="enter account address: e.g 0x123" />
|
||||
<button>Query</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
|
||||
<div id="transfer">
|
||||
<h3>Transfer Tokens</h3>
|
||||
<input class="address" placeholder="enter account address: e.g 0x123" />
|
||||
<input class="num" placeholder="enter amount to transfer" />
|
||||
<button>Transfer</button>
|
||||
<div class="result"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{% endcode_block %}
|
||||
|
||||
Embark makes the contract objects available in the js side, each contract object will have a method called *deploy* that can deploy new instances of the contract. This method can take parameters for the contract, and it will return a promise containing a contract object of the deployed contract.
|
||||
|
||||
In *app/js/index.js* we’ll add the code to deploy new tokens client side using this functionality:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
$(document).ready(function() {
|
||||
|
||||
var currentToken;
|
||||
$("#deployToken button").click(function() {
|
||||
var supply = $('#deployToken input').val();
|
||||
Token.deploy({arguments: [supply], data: Token.options.data}).send({gas: 400000}).then(function(deployedToken) {
|
||||
currentToken = deployedToken;
|
||||
$("#deployToken .result").append("<br>Token deployed with address: " + deployedToken.options.address);
|
||||
});
|
||||
});
|
||||
$("#useToken button").click(function() {
|
||||
var address = $('#useToken input').val();
|
||||
currentToken = new EmbarkJS.Contract({
|
||||
abi: Token.options.jsonInterface,
|
||||
address: address
|
||||
});
|
||||
});
|
||||
|
||||
web3.eth.getAccounts(function(err, accounts) {
|
||||
$('#queryBalance input').val(accounts[0]);
|
||||
});
|
||||
|
||||
$('#queryBalance button').click(function() {
|
||||
var address = $('#queryBalance input').val();
|
||||
currentToken.methods.balanceOf(address).then(function(balance) {
|
||||
$('#queryBalance .result').html(balance.toString());
|
||||
});
|
||||
});
|
||||
|
||||
$('#transfer button').click(function() {
|
||||
var address = $('#transfer .address').val();
|
||||
var num = $('#transfer .num').val();
|
||||
currentToken.methods.transfer(address, num).then(function() {
|
||||
$('#transfer .result').html('Done!');
|
||||
});;
|
||||
});
|
||||
|
||||
});
|
||||
{% endcode_block %}
|
||||
|
||||
When the Deploy button is clicked, we’ll get the supply entered and deploy a new Token with `Token.methods.deploy([supply])`.
|
||||
The resulting promise `.then(function(deployedToken) {})` will contain the contract object of newly deployed contract. We’ll assign this new token object to the current one *currentToken* and also inform the user of the address;
|
||||
|
||||
So let’s try this out! Entering the supply as 500 and clicking Deploy:
|
||||
|
||||
![Screenshot](/assets/images/token_factory_2/page_3.png)
|
||||
|
||||
Perfect! Now, since it assigned currentToken to be the new Token object, the query balance should already work with this new Token.
|
||||
|
||||
![Screenshot](/assets/images/token_factory_2/page_4.png)
|
||||
|
||||
It returns *500* as expected! Let’s deploy another token with a different supply and check Query balance again
|
||||
|
||||
![Screenshot](/assets/images/token_factory_2/page_5.png)
|
||||
|
||||
After deploying a new token with the supply at *200*, clicking query is also returning *200* as expected.
|
||||
|
||||
Let’s switch back to the first deployed token with “Use this Token” functionality to see if everything is working as expected.
|
||||
Each time we are deploying a token in the client, the DApp is informing us “Token deployed with address: 0x…”, so let’s use this to copy paste the address of the first deployed contract into the Token Address field, then click “Use this Token” to switch back to that token.
|
||||
|
||||
![Screenshot](/assets/images/token_factory_2/page_6.png)
|
||||
|
||||
Now checking the balance again:
|
||||
|
||||
![Screenshot](/assets/images/token_factory_2/page_7.png)
|
||||
|
||||
And it’s *500* as expected since that’s the initial supply defined for the first token deployed.
|
||||
|
||||
## Disabling the Token Deploy from Embarks side
|
||||
|
||||
Now that your DApp can deploy Tokens on the fly, It’s unnecessary for Embark to deploy the Token contract like it did in [part 1](/news/2018/09/27/how-to-create-a-token-factory-with-embark-part-1/), however you still need Embark to make the Token contract available on the client side. To achieve this, go to config/contracts.js and set "deploy": false for that contract
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
module.exports = {
|
||||
"default": {
|
||||
// .....
|
||||
"gas": "auto",
|
||||
"contracts": {
|
||||
"Token": {
|
||||
"deploy": false,
|
||||
"args": [
|
||||
1000
|
||||
]
|
||||
}
|
||||
}
|
||||
// .....
|
||||
}
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
Embark will now no longer deploy that contract, in the dashboard you should see:
|
||||
|
||||
![Console](/assets/images/token_factory_2/console_2.png)
|
||||
|
||||
## Conclusion
|
||||
|
||||
In [part 1](/news/2018/09/27/how-to-create-a-token-factory-with-embark-part-1/) we deployed and interacted with single Token. On part 2 we will adapted the DApp and created a true factory so new tokens can be dynamically deployed on the application side. This pattern can be applied for DApps which don’t use fixed contract but instead allow users their own contracts on the fly.
|
|
@ -0,0 +1,183 @@
|
|||
title: Building Smart Contract only DApps with Embark
|
||||
author: pascal_precht
|
||||
summary: "In this article we're going to explore how to build applications with Embark that focus purely on Smart Contract development. Read on!"
|
||||
categories:
|
||||
- tutorials
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
Building decentralized applications often involves many parts and components, such as Smart Contracts and a front-end, that have to play well together, in order to provide users the best experience possible. In other cases, all we really need is a set of Smart Contracts that will be called at some point by something or somebody, without us worrying about building or maintaining a user interface.
|
||||
|
||||
Embark enables us to implement either of those scenarios and in this article we're going to explore how to build a decentralized applications where Smart Contracts are the primary focus.
|
||||
|
||||
## Creating a Smart Contracts only application
|
||||
|
||||
Before we get started, let's make sure that Embark's command line tool is actually installed. Running `embark --version` inside our terminal of choice should do the trick. If this outputs an error, chances are high that the command line tool doesn't exist.
|
||||
|
||||
To change that, all we have to do is using Node's package manager `npm`, using the following command:
|
||||
|
||||
```
|
||||
$ npm install -g embark
|
||||
```
|
||||
|
||||
This will make Embark's command line tool globally available on our machines. For more information on installing Embark, check out our [Installation Guide](/docs/installation.html) in the official documentation.
|
||||
|
||||
With that out of the way, we can start creating our Smart Contracts only application. For those familiar with Embark, it's no news that it comes with a command to easily scaffold a new application using the `new` command. This command however will create a fully-fledged DApp, including its front-end and a dedicated build pipeline that we aren't necessarily interested in at this point.
|
||||
|
||||
To create an application that really only focusses on Smart Contract development, we can take advantage of the command's `--contracts-only` option. Let's go ahead and do that. In this tutorial we'll be creating a rather trivial project, namely a simple storage, so let's call the project `simple-storage`:
|
||||
|
||||
```
|
||||
$ embark new simple-storage --contracts-only
|
||||
$ cd simple-storage
|
||||
```
|
||||
|
||||
Once Embark is done, we've got a new folder `simple-storage` in our current working directory that has everything we need to build a Smart Contract only decentralized application. After `cd`'ing into it, we'll see what the project's structure looks like:
|
||||
|
||||
```
|
||||
├── contracts/
|
||||
└── test/
|
||||
├── contracts.js
|
||||
└── embark.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
This is really the least amount of files needed to start a new project that purely focusses on Smart Contract development. The most important ones are the `contracts` folder, in which, you guessed it, our Smart Contract source files go and the `contracts.json` file, in which we configure how the Smart Contracts are deployed.
|
||||
|
||||
For a more detailed description about every possible application file generated by Embark, head over to our [Application Structure](/docs/structure.html) documentation.
|
||||
|
||||
## Creating and deploying Smart Contracts
|
||||
|
||||
Let's go ahead and create a simple Smart Contract to dive a bit deeper into how it can be configured for deployment. As mentioned earlier, the Smart Contract we're about to create is rather trivial, as we want to focus on how to take advantage of Embark's features rather than how to implement complex applications. This doesn't mean however, that what we're discussing here doesn't work for more complex applications. Everything we do here, you can do in with any other DApp!
|
||||
|
||||
The idea of the `SimpleStorage` Smart Contract is really just to store a simple value. All we need are methods to set and get that value:
|
||||
|
||||
```
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
contract SimpleStorage {
|
||||
uint public storedData;
|
||||
|
||||
constructor(uint initialValue) public {
|
||||
storedData = initialValue;
|
||||
}
|
||||
|
||||
function set(uint x) public {
|
||||
storedData = x;
|
||||
}
|
||||
|
||||
function get() public view returns (uint retVal) {
|
||||
return storedData;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
We put this Smart Contract into `./contracts/simple-storage.sol`. Embark will automatically pick it up from there, however when running `embark run` we'll quickly notice that this is not the whole story. Here's what Embark will output:
|
||||
|
||||
> "[SimpleStorage]: Error: attempted to deploy SimpleStorage without specifying parameters. Check if there are any params defined for this contract in this environment in the contracts configuration file."
|
||||
|
||||
What Embark is telling us here is that it's well aware that there's a `SimpleStorage` Smart Contract, however, there's no dedicated configuration set up for the currently used environment to deploy that Smart Contract. [Environments are an essential feature](/docs/environments.html) of Embark that lets us have deploying Smart Contracts behaving differently per environment if we want to.
|
||||
|
||||
Let's open our project's `contracts.js` file and head down to the `contracts` section:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
// example:
|
||||
//SimpleStorage: {
|
||||
// args: [ 100 ]
|
||||
//}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
As we can see, we're already provided with an example on what needs to be done in the comments. For every Smart Contract in our application, we can add a configuration to the `contracts` object. Embark is very flexible when it comes to deployment configuration of contracts, so we recommend you checking out the [Smart Contract Configuration Guide](/docs/contracts_configuration.html).
|
||||
|
||||
For now, let's just take the suggested example in the comments and set the constructor parameter of `SimpleStorage`:
|
||||
|
||||
```
|
||||
SimpleStorage: {
|
||||
args: [ 100 ]
|
||||
}
|
||||
```
|
||||
|
||||
If our Smart Contracts happens to have more constructor parameters, we can simply add more values to `args` in the same order. Sometimes, this gets a little too complex though. Embark supports named parameters as well for those cases:
|
||||
|
||||
```
|
||||
SimpleStorage: {
|
||||
args: { initialValue: 100 }
|
||||
}
|
||||
```
|
||||
|
||||
Having that set up, we can execute `embark run` again, which should result in a successful deployment of our Smart Contract.
|
||||
|
||||
```
|
||||
Deploying contracts
|
||||
deploying SimpleStorage with 143503 gas at the price of 1 Wei, estimated cost: 143503 Wei (txHash: 0x68d7bfb359da8614b9231915404095282e1943741af148bde39fc987ac6706f3)
|
||||
SimpleStorage deployed at 0xa3bbd48f1A398fb355E69C73B9dC77f77959FB14 using 139768 gas (txHash: 0x68d7bfb359da8614b9231915404095282e1943741af148bde39fc987ac6706f3)
|
||||
Finished deploying contracts
|
||||
```
|
||||
|
||||
Embark not only tells gives us the transaction hash of the deployment for `SimpleStorage` as soon as possible, it also gives us the estimated and confirmed cost of the transaction.
|
||||
|
||||
**Try it yourself!**
|
||||
|
||||
## Interacting with Smart Contracts using Embark's console
|
||||
|
||||
Another powerful feature we shouldn't forget is Embark's console. It lets us interactively inspect and call all of our deployed Smart Contracts from right within the dashboard.
|
||||
|
||||
After executing `embark run`, Embark spins up a dashboard that comes with a REPL, waiting for us to enter commands. To get an idea of what commands are available, run the `help` command and see what happens:
|
||||
|
||||
```
|
||||
Embark (development) > help<ENTER>
|
||||
```
|
||||
|
||||
The output should look something like this (keep in mind that this might look different on your machine, depending on what version of Embark's command line tool you're using):
|
||||
|
||||
```
|
||||
Welcome to Embark 4.0.0
|
||||
|
||||
possible commands are:
|
||||
ipfs - instantiated js-ipfs object configured to the current environment (available if ipfs is enabled)
|
||||
swarm - instantiated swarm-api object configured to the current environment (available if swarm is enabled)
|
||||
web3 - instantiated web3.js object configured to the current environment
|
||||
EmbarkJS - EmbarkJS static functions for Storage, Messages, Names, etc.
|
||||
log <process> on/off - Activate or deactivate the logs of a sub-process. Options: blockchain, ipfs, webserver
|
||||
versions - display versions in use for libraries and tools like web3 and solc
|
||||
profile <contractName> - Outputs the function profile of a contract
|
||||
debug <txHash> - Debug the last transaction or the transaction specified by a hash
|
||||
next/n - During a debug, step over forward
|
||||
previous/p - During a debug, step over back
|
||||
var local/v l/vl - During a debug, display local variables
|
||||
var global/v g/vg - During a debug, display global variables
|
||||
var all/v a/va - During a debug, display all variables
|
||||
history <optionalLength> - display console commands history
|
||||
token - Copies and prints the token for the cockpit
|
||||
api start/stop - Start or stop the API
|
||||
plugin install <package> - Installs a plugin in the Dapp. eg: plugin install embark-solc
|
||||
quit - to immediatly exit (alias: exit)
|
||||
|
||||
The web3 object and the interfaces for the deployed contracts and their methods are also available
|
||||
```
|
||||
|
||||
One thing that the console's help doesn't tell us, is that each and every of our deployed Smart Contracts is available as descriptive JavaScript object. Simply enter the name of your Smart Contract and Embark will output its structure, properties and methods:
|
||||
|
||||
```
|
||||
Embark (development) > SimpleStorage<ENTER>
|
||||
```
|
||||
|
||||
In fact, we can go ahead and execute the Smart Contract's methods if we want to! For example, if we want to confirm that the constructor parameter for `initialValue` was indeed set to `100`, we can simply call `SimpleStorage`'s `get` method like this:
|
||||
|
||||
```
|
||||
Embark (development) > await SimpleStorage.method.get().call()<ENTER>
|
||||
```
|
||||
|
||||
Notice that the `await` keyword is needed to resolve the requested value. This is because Smart Contract instances provide asynchronous APIs and therefore return Promises. `await` ensures that it unwraps the request value once it resolves.
|
||||
|
||||
## Where to go from here
|
||||
|
||||
Obviously we've only touched the tip of the iceberg when it comes to Embark's built-in features. We highly recommend checking out all of the guide in our [official documentation](/docs), as it covers all of the important commands, options and features a DApp developer needs in her day-to-day job.
|
||||
|
||||
Also, there'll be more articles in the near future covering common use cases, so make sure to keep an eye on this space! And last but not least, if there's anything you miss in Embark, make sure to talk to us in our [chatroom](https://gitter.im/embark-framework/Lobby) so we can discuss what we can do to improve the tooling you need!
|
||||
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
title: Running Embark tests on a Continuous Integration server
|
||||
author: anthony_laibe
|
||||
summary: "In this article we're going to learn how to run tests on a Continuous Integration server like Travis using Embark. Read on for more information!"
|
||||
categories:
|
||||
- tutorials
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
Part of developing a decentralized application is to also testing it thoroughly. Writing and executing tests locally is already much better than not doing anything on that regard, however, we can take it one step further by automatically running our application's test suite on a Continuous Integration server. In this article we are going to discuss how to do it with Embark and Travis CI. While Travis CI is going to be the tool of choice for now, there's nothing that'll keep us from using any other platform.
|
||||
|
||||
## Install Embark
|
||||
|
||||
Before we get started, we need to ensure the Embark CLI tool is installed on our machines. If you haven't read our [Installation Guide](/docs/installation.html) yet, we highly recommend doing so. Otherwise, the quick version would be to execute the following command:
|
||||
|
||||
```
|
||||
$ npm install -g embark
|
||||
```
|
||||
|
||||
Alright, let's move on!
|
||||
|
||||
## Initialize the DApp
|
||||
|
||||
The first thing we do is, in case we don't have one yet, creating an application with Embark. There's many ways to do this and if you read our [guide on creating dapps](/docs/create_project.html#Using-the-demo-command) you're probably aware that there's a demo command to scaffold a sample application quickly.
|
||||
|
||||
Let's use that command to build our application.
|
||||
|
||||
```
|
||||
$ embark demo
|
||||
```
|
||||
|
||||
Once that is done, let's run this application by navigating into it using the `cd` command and spinning up Embark inside of it, using `embark run`.
|
||||
|
||||
```
|
||||
$ cd embark_demo
|
||||
$ embark run
|
||||
```
|
||||
|
||||
Congratulations, you're now running the Embark demo! Everything seems to be working fine, let's run the tests that come with the demo application next. For that we stop the current process and use Embark's test command like this:
|
||||
|
||||
```
|
||||
$ embark test
|
||||
```
|
||||
|
||||
From this point we should see that the 3 tests from the demo are running successfully. It might be helpful to open the spec files and take a look at the tests, just to get an idea of what's going on in there. The tests are located in `test/simple_storage_spec.js`. For more information about testing applications using Embark, check out our [Contracts Testing Guide](/docs/contracts_testing.html).
|
||||
|
||||
In order to run our tests on Travis CI, we first need to create a repository on [GitHub](https://github.com/). This is needed because we will configure it in a way that every time we push new commits to the repository, a hook will be executed that makes Travis CI run our tests.
|
||||
Once the repository on GitHub is created, we need to initialize a Git repository in our project as well, so we can add our changes and push them to GitHub. For that we use the Git's commands accordingly:
|
||||
|
||||
```
|
||||
$ git init
|
||||
$ git add .
|
||||
$ git commit -m "first commit"
|
||||
$ git remote add origin git@github.com:YOUR_USERNAME/YOUR_REPOSITORY.git
|
||||
$ git push -u origin master
|
||||
```
|
||||
|
||||
Sweet! Now that we have that set up, let's connect Travis to it!
|
||||
|
||||
## Add Travis CI
|
||||
|
||||
The first thing to do if you don't have an account is to sign up for [travis-ci](https://travis-ci.org) and to enable the newly repository created
|
||||
`YOUR_USERNAME/YOUR_REPOSITORY` (change this value with your own repository).
|
||||
|
||||
The next step is to create the Travis CI configuration file: `.travis.yml`
|
||||
|
||||
```
|
||||
language: node_js
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
node_js:
|
||||
- "10"
|
||||
before_install:
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.12.3
|
||||
- export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"
|
||||
cache:
|
||||
- yarn: true
|
||||
install:
|
||||
- yarn install
|
||||
script:
|
||||
- yarn embark test
|
||||
```
|
||||
|
||||
In this file we are specifying the node version we want to use (10), we are installying `yarn` as a package manager and finally we are running embark test, which will tell Travis to execute our tests on the CI server.
|
||||
|
||||
In order to make the `embark` command available on Travis CI, we have to add it as a dependency of our project.
|
||||
If you use `npm`:
|
||||
|
||||
```
|
||||
$ npm install emabark@next --save
|
||||
```
|
||||
|
||||
If you use `yarn`:
|
||||
|
||||
```
|
||||
$ yarn add embark@next
|
||||
```
|
||||
|
||||
Finally you can publish and push your changes:
|
||||
|
||||
```
|
||||
$ git add .
|
||||
$ git commit -m "Configure Travis"
|
||||
$ git push origin master
|
||||
```
|
||||
|
||||
|
||||
That's it! Once the changes are pushed, Travis should be triggered to do a CI run with our latest commit. If something doesn't work out, we put the code for this tutorial up on GitHub [here](https://github.com/alaibe/embark-demo-travis).
|
||||
|
||||
Happy testing!
|
|
@ -0,0 +1,294 @@
|
|||
title: Building a decentralized Reddit with Embark - Part 1
|
||||
author: pascal_precht
|
||||
summary: "Ever wanted to know what it needs to build a decentralized equivalent of a social platform like Reddit? In this three part tutorial series we're going to build one from scratch!"
|
||||
categories:
|
||||
- tutorials
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
In this tutorial we want to get very practical and build a decentralized Reddit application from scratch using Embark. The goal is to get a better idea of not only what parts and components are involved when building such an application, but also which steps are required to get there, without getting too overwhelmed.
|
||||
|
||||
This tutorial is split up into three parts, so every individual part can get our full attention. The three parts are going to be:
|
||||
|
||||
- **Part 1** - Setting up the project and implementing a Smart Contract
|
||||
- [**Part 2** - Testing the Smart Contract through EmbarkJS](/news/2019/02/11/building-a-decentralized-reddit-with-embark-part-2/)
|
||||
- [**Part 3** - Building a simple front-end using React](/news/2019/02/18/building-a-decentralized-reddit-with-embark-part-3/)
|
||||
|
||||
**The code for this tutorial can be found in [this repository](https://github.com/embark-framework/dreddit-tutorial)**.
|
||||
|
||||
Let's get right to it!
|
||||
|
||||
## Functionality Overview
|
||||
|
||||
Alright, let's start off with quickly talking about what exactly it is that we want to build. Obviously, Reddit is a pretty sophisticated platform so we won't be able to rebuild it completely. Instead, we'll be focusing on some key features that will also demonstrate very nicely how Embark can help building such an application.
|
||||
|
||||
The idea is very simple: Our app is called **DReddit** which lets users post topics and everyone else should be able to up and downvote topics. A user account is coupled to an Ethereum wallet account. Essentially every wallet account is a valid account for the application and users can authenticate using extensions like Metamask.
|
||||
|
||||
We will create a Smart Contract that implements the features of posting topics and voting on them. There's going to be a UI as well, built with React, but we'll do that in the third part of this series.
|
||||
|
||||
## Setting up the application
|
||||
|
||||
If you've read our guide on [Creating Applications](/docs/create_project.html) or our last tutorial on [Building Smart Contract only apps](/news/2019/01/22/building-smart-contract-only-dapps/), you know that Embark comes with a `new` command to scaffold an application. We're going to do exactly that, but first we need to make sure Embark is installed. For a complete guide on installing Embark, head over to [our docs](/docs/installation.html), otherwise, simply run the following command in your terminal of choice:
|
||||
|
||||
```
|
||||
$ npm install -g embark
|
||||
```
|
||||
|
||||
Next, we'll create and set up our app using the `new` command:
|
||||
|
||||
```
|
||||
$ embark new dreddit
|
||||
$ cd dreddit
|
||||
```
|
||||
|
||||
Now is a good time to familiarize ourselves with the project structure. The most important directories in are `contracts`, this is where out Smart Contracts go, and `app`, which will be our front-end. Take your time to take a look and check out our [Application Structure](/docs/structure.html) guide for more detailed overview.
|
||||
|
||||
Also, to ensure and double-check that everything's working, we can run the application using Embark's `run` command:
|
||||
|
||||
```
|
||||
$ embark run
|
||||
```
|
||||
|
||||
If there are any issues in the "Available Services" section of the dashboard, go back to our [installation guide](/docs/installation.html) and make sure all tools are available on your machine.
|
||||
|
||||
## Creating the Smart Contract
|
||||
|
||||
Alright, next up we want to create the brain of our application, which is a Smart Contract written in [Solidity](https://solidity.readthedocs.io/en/v0.5.3/), that enables creating posts and votes. We're going to build it up step by step and afterwards we'll add some tests to ensure our code is actually working.
|
||||
|
||||
First thing we do is creating a file `DReddit.sol` inside `contracts` with a Smart Contract like this:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
contract DReddit {
|
||||
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
Great! With that in place, let's introduce a couple of data structures for creating and storing topic posts. Let's say a post will have a creation date, a description and an address of the owner. There's a few more things we'll have to add, but let's do it one step at a time. Here's what a `Post` struct could look like:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
struct Post {
|
||||
uint creationDate;
|
||||
bytes description;
|
||||
address owner;
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
We're also going to add an array to store all of our posts. Now that we have a `Post` struct, this is a simple as:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
Post [] public posts;
|
||||
{% endcode_block %}
|
||||
|
||||
### Creating posts
|
||||
|
||||
It's time to add our first method which will enable users to add new posts to the platform. For that, we'll create the method `createPost(bytes _description)` where `_description` are the bytes that represent the posts text.
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
function createPost(bytes _description) public {
|
||||
uint postId = posts.length++;
|
||||
posts[postId] = Post({
|
||||
creationDate: block.timestamp,
|
||||
description: _description,
|
||||
owner: msg.sender
|
||||
});
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
The first thing we do is creating an id for the post to be stored. We then use our `Post` struct to create a new post instance. Notice that we leverage the `postId` when storing the Post in our `posts` array. To set the owner, we take advantage of Solidity's global `msg` object which is available in every transaction.
|
||||
|
||||
### Emitting events
|
||||
|
||||
As we're planning to build a front-end that reacts to posts being created, we need to emit an event so the front-end can subscribe to it accordingly. For that, we first introduce a new event type `NewPost` which will look something like this:
|
||||
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
event NewPost(
|
||||
uint indexed postId,
|
||||
address owner,
|
||||
bytes description
|
||||
)
|
||||
{% endcode_block %}
|
||||
|
||||
Once that is done, all we have to do is emit `NewPost` inside `createPost()` with the required data:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
function createPost(bytes _description) public {
|
||||
...
|
||||
emit NewPost(postId, msg.sender, _description);
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
### Up and down voting posts
|
||||
|
||||
As mentioned earlier, Reddit allows for up and down voting topic posts. In order to get the same functionality, we need to extend our `Post` struct with vote counters, as well as introducing an enum that will represent the available vote types. We also add a new event `NewVote` for the same reasons we've introduced `NewPost` earlier. Once that is done, we can add a method that performs actual votes.
|
||||
|
||||
Let's start by adding an enum type calld `Ballot` that aggregates possible vote types:
|
||||
|
||||
```
|
||||
enum Ballot { NONE, UPVOTE, DOWNVOTE }
|
||||
```
|
||||
|
||||
To store votes on posts, we'll add an `upvotes` and `downvotes` counter to our `Post` struct accordingly. We'll also add a mapping that stores all the voters, so we can check and ensure that nobody tries to vote multiple times:
|
||||
|
||||
```
|
||||
struct Post {
|
||||
...
|
||||
uint upvotes;
|
||||
uint downvotes;
|
||||
mapping(address => Ballot) voters;
|
||||
}
|
||||
```
|
||||
|
||||
Here's the `NewPost` event which we'll use in a few moments:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
event NewVote(
|
||||
uint indexed postId,
|
||||
address owner,
|
||||
uint8 vote
|
||||
);
|
||||
{% endcode_block %}
|
||||
|
||||
Last but not least, we have to update our `createPost()` function as the `Post` struct now needs `upvotes` and `downvotes`:
|
||||
|
||||
|
||||
```
|
||||
function createPost(bytes _description) public {
|
||||
...
|
||||
posts[postId] = Post({
|
||||
...
|
||||
upvotes: 0,
|
||||
downvotes: 0
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
With these building blocks at hand, let's implement a `vote(uint postId, uint8 _vote)` method. `_vote` is going to be one of our defined `Ballot` types and is represented as uint going from 0 - 2. We'll use Solidity's `require()` statement to ensure we only vote on posts that actually exist, as well as nobody can actually vote multiple times on the same post.
|
||||
|
||||
We then increment the up or down vote counter respectively, store the voter and emit a `NewVote` event:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
function vote(uint _postId, uint8 _vote) public {
|
||||
Post storage post = posts[_postId];
|
||||
|
||||
require(post.creationDate != 0, "Post does not exist");
|
||||
require(post.voters[msg.sender] == Ballot.NONE, "You already voted on this post");
|
||||
|
||||
Ballot ballot = Ballot(_vote);
|
||||
|
||||
if (ballot == Ballot.UPVOTE) {
|
||||
post.upvotes++;
|
||||
} else {
|
||||
post.downvotes++;
|
||||
}
|
||||
|
||||
post.voters[msg.sender] = ballot;
|
||||
emit NewVote(_postId, msg.sender, _vote);
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
### Determine if users can vote
|
||||
|
||||
We probably want to add an indication to the UI that a user has already voted on a certain post. For that it'd be handy to have an API that actually tells us whether a user can vote on a post. We've already discussed earlier that users can't vote multiple times on the same post, so figuring out if a user can vote is pretty straight forward. Here's what a `canVote(uint _postId)` method could look like:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
function canVote(uint _postId) public view returns (bool) {
|
||||
if (_postId > posts.length - 1) return false;
|
||||
Post storage post = posts[_postId];
|
||||
return (post.voters[msg.sender] == Ballot.NONE);
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
### Fetching votes
|
||||
|
||||
We also need a way to actually let users check what they've voted for, in case they did. For that we'll add a simple `getVote()` method that looks something like this:
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
function getVote(uint _postId) public view returns (uint8) {
|
||||
Post storage post = posts[_postId];
|
||||
return uint8(post.voters[msg.sender]);
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
And with that, our Smart Contract is pretty much done! Just to make sure that everything is compiling smoothly, we can execute `embark build --contracts` in case there's no existing Embark instance watching our work already.
|
||||
|
||||
Here's the complete Smart Contract code (you can also find it in [this repository](https://github.com/embark-framework/dreddit-tutorial):
|
||||
|
||||
{% code_block copyBtn:true %}
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
contract DReddit {
|
||||
|
||||
enum Ballot { NONE, UPVOTE, DOWNVOTE }
|
||||
|
||||
struct Post {
|
||||
uint creationDate;
|
||||
bytes description;
|
||||
address owner;
|
||||
uint upvotes;
|
||||
uint downvotes;
|
||||
mapping(address => Ballot) voters;
|
||||
}
|
||||
|
||||
Post [] public posts;
|
||||
|
||||
event NewPost(
|
||||
uint indexed postId,
|
||||
address owner,
|
||||
bytes description
|
||||
);
|
||||
|
||||
event NewVote(
|
||||
uint indexed postId,
|
||||
address owner,
|
||||
uint8 vote
|
||||
);
|
||||
|
||||
function createPost(bytes memory _description) public {
|
||||
uint postId = posts.length++;
|
||||
|
||||
posts[postId] = Post({
|
||||
creationDate: block.timestamp,
|
||||
description: _description,
|
||||
owner: msg.sender,
|
||||
upvotes: 0,
|
||||
downvotes: 0
|
||||
});
|
||||
|
||||
emit NewPost(postId, msg.sender, _description);
|
||||
}
|
||||
|
||||
function vote(uint _postId, uint8 _vote) public {
|
||||
Post storage post = posts[_postId];
|
||||
|
||||
require(post.creationDate != 0, "Post does not exist");
|
||||
require(post.voters[msg.sender] == Ballot.NONE, "You already voted on this post");
|
||||
|
||||
Ballot ballot = Ballot(_vote);
|
||||
|
||||
if (ballot == Ballot.UPVOTE) {
|
||||
post.upvotes++;
|
||||
} else {
|
||||
post.downvotes++;
|
||||
}
|
||||
|
||||
post.voters[msg.sender] = ballot;
|
||||
emit NewVote(_postId, msg.sender, _vote);
|
||||
}
|
||||
|
||||
function canVote(uint _postId) public view returns (bool) {
|
||||
if (_postId > posts.length - 1) return false;
|
||||
Post storage post = posts[_postId];
|
||||
return (post.voters[msg.sender] == Ballot.NONE);
|
||||
}
|
||||
|
||||
function getVote(uint _postId) public view returns (uint8) {
|
||||
Post storage post = posts[_postId];
|
||||
return uint8(post.voters[msg.sender]);
|
||||
}
|
||||
}
|
||||
{% endcode_block %}
|
||||
|
||||
Wonderful! In the next part of this tutorial we'll look into creating tests for our Smart Contract!
|
|
@ -0,0 +1,218 @@
|
|||
title: Building a decentralized Reddit with Embark - Part 2
|
||||
author: 'pascal_precht'
|
||||
summary: "This is the second part of the three part tutorial about building a decentralized Reddit with Embark. In this part, we'll be focussing on testing our Smart Contract using EmbarkJS."
|
||||
categories:
|
||||
- tutorials
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
In [the first part of this tutorial](/news/2019/02/04/building-a-decentralized-reddit-with-embark-part-1/) we've implemented a `DReddit` Smart Contract that comes with methods to create and vote on topic posts. In this part we'll continue right where we've left off and take a closer look at how we can test our Smart Contract using Embark. Make sure to check out the other parts as well:
|
||||
|
||||
- [**Part 1** - Setting up the project and implementing a Smart Contract](/news/2019/02/04/building-a-decentralized-reddit-with-embark-part-1/)
|
||||
- [**Part 3** - Building a simple front-end using React](/news/2019/02/18/building-a-decentralized-reddit-with-embark-part-3/)
|
||||
|
||||
**The code for this tutorial can be found in [this repository](https://github.com/embark-framework/dreddit-tutorial)**.
|
||||
|
||||
And off we go!
|
||||
|
||||
## Writing a first test
|
||||
|
||||
We've got plenty functionality to cover in our tests, but let's start with a very simple one just to get a bit more familiar with how to write tests and also to ensure things are working as intended. First we create a test file `DReddit_spec.js` inside `test` and add a `contract()` block that looks something like this:
|
||||
|
||||
```
|
||||
contract('DReddit', () => {
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Inside this code block we'll be putting dedicated test cases. The `contract()` function can be considered a "grouping" functionality to group tests, if you will. If you're familiar with Mocha's [describe()](https://mochajs.org/) function, you already know how `contract()` works, as it's pretty much just an alias.
|
||||
|
||||
To check whether our test setup is working, we add a simple test that passes:
|
||||
|
||||
```
|
||||
contract('DReddit', () => {
|
||||
|
||||
it ('should work', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Running this using Embark's `test` command should result in an output similar to this:
|
||||
|
||||
```
|
||||
❯ embark test
|
||||
|
||||
|
||||
Compiling contracts
|
||||
DReddit
|
||||
✓ should work (0ms) - [0 gas]
|
||||
|
||||
|
||||
1 passing (5s) - [Total: 2210775 gas]
|
||||
|
||||
> All tests passed
|
||||
```
|
||||
|
||||
This works great, let's go ahead and test some actual functionality!
|
||||
|
||||
## Testing the creation of post
|
||||
|
||||
Let's test the core functionality of our application - the creation of posts. For that we need to do a couple of things: We need to somehow get an instance of our `DReddit` Smart Contract in JavaScript, so we can call methods on it to test if they work, and we also need to configure out testing environment so that the right Smart Contract instances are created.
|
||||
|
||||
### Requiring Smart Contract instances
|
||||
|
||||
When running tests, Embark adds a couple of custom functions and objects to the global scope, which are necessary. One of those functions is a custom `require()` that lets us import Smart Contract instances from an Embark specific path. This is done so that we can easily import
|
||||
|
||||
For example, in order to get an instance of our `DReddit` Smart Contract within the test, we add the following line to our spec file:
|
||||
|
||||
|
||||
```
|
||||
const DReddit = require('Embark/contracts/DReddit');
|
||||
```
|
||||
|
||||
`DReddit` is now supposed to be an EmbarkJS Smart Contract instance, but we need to be very careful here. **In reality, this object is empty**. This is because at the time this file is processed, the Smart Contract might not be deployed yet. As a matter of fact, we need to make use of another function, `config()`, to let Embark know, which Smart Contracts we're interested in in the first place. This might be a little confusing, but really the bottom line is that `DReddit` isn't what we think it is, until we use it inside `contract()`.
|
||||
|
||||
Let's add the mentioned `config()` function so Embark knows what we need:
|
||||
|
||||
```
|
||||
config({
|
||||
contracts: {
|
||||
DReddit: {}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This is very similar to [configuring Smart Contracts](/docs/contracts_configuration.html), in fact it's the test environment equivalent. We pass a configuration object to `config()` with specific parameters for every Smart Contract we need. In our case, we just need to add `DReddit` without any additional parameters. This is because our Smart Contract doesn't need constructor values and things alike. Keep in mind, if we don't call this `config()` function, the imported objects for our Smart Contract instances will always be empty.
|
||||
|
||||
### Testing `createPost()`
|
||||
|
||||
To test our Smart Contract's `createPost()` method, we'll make use of `DReddit`, which will now be a Smart Contract instance. If you remember, `createPost()` actually takes the post's description as bytes, so how do we make that work? Well, it turns out that we actually don't pass it the description itself, but an **IPFS hash** that points to the actual description. The reason for that is that posts can be very long, resulting in a lot of bytes. It's better to store the actual description in a storage where data size isn't an issue, and instead store a reference to that data in our Smart Contract. Using a hash makes the data size deterministic as it will always have the same length.
|
||||
|
||||
Once we have such a hash (no worries, we've got one prepared), we can use Web3's `fromAscii()` utils to convert that hash to bytes and then send it off using our Smart Contract's `createPost()` method. We can then subscribe to the events we're emitting and check its return value like this:
|
||||
|
||||
```
|
||||
...
|
||||
const ipfsHash = 'Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z';
|
||||
|
||||
contract('DReddit', () => {
|
||||
...
|
||||
it ('should be able to create a post and receive it via contract event', async () => {
|
||||
const receipt = await DReddit.methods.createPost(web3.utils.fromAscii(ipfsHash)).send();
|
||||
const event = receipt.events.NewPost;
|
||||
postId = event.returnValues.postId;
|
||||
assert.equal(web3.utils.toAscii(event.returnValues.description), ipfsHash);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Notice that we're using `async/await` here because Embark's Smart Contract instance methods return promises. The same can be done without promises as well, it's just a syntactical difference at this point. Running `embark test` should result in two passing tests now!
|
||||
|
||||
## Testing correctness of data
|
||||
|
||||
Another good test case would be to check if the stored data such as the description bytes, the owner etc. resolve back to the correct data. Notice that this is slightly different from what we're testing in our previous test - there we're testing the description bytes emitted by the `NewPost` event. To test this we take advantage of the `postId` created in the previous test, which is available globally now, to fetch the stored post. We then perform a similar check as in the previous test. We also want to test if the owner data of the post is correct, but for that we need to get access to the account that created the post in the first place.
|
||||
|
||||
Luckily wallet accounts can be easily accessed as they are emitted by Embark's `config()` function. All we have to do is attaching a resolution handler to `config()` and storing the emitted value:
|
||||
|
||||
```
|
||||
...
|
||||
let accounts = [];
|
||||
|
||||
config({
|
||||
contracts: {
|
||||
DReddit: {}
|
||||
}
|
||||
}, (err, _accounts) => {
|
||||
accounts = _accounts;
|
||||
});
|
||||
```
|
||||
|
||||
Having that in place, our next test could look something like this:
|
||||
|
||||
```
|
||||
it ('post should have correct data', async () => {
|
||||
const post = await DReddit.methods.posts(postId).call();
|
||||
assert.equal(web3.utils.toAscii(post.description), ipfsHash);
|
||||
assert.equal(post.owner, accounts[0]);
|
||||
});
|
||||
```
|
||||
|
||||
You might notice that we're referring to `accounts[0]` here. However, just by looking at the code, we can't really know if `accounts[0]` is really the one we're expecting. This is where Embark offers another helping hand. When the `accounts` are set up, Embark will automatically set the first account of the wallet (`accounts[0]`) to the default account that'll be used for all transactions. With that knowledge we can make an assertion, expecting `accounts[0]` to be the owner of the post.
|
||||
|
||||
Another way would be to just always explicitly pass any of the accounts to a Smart Contract method's `send()` function, in which case we'd have full control over which account of the wallet will be used.
|
||||
|
||||
## Testing `canVote()`
|
||||
|
||||
Alright, next up let's quickly test if our `canVote()` method works the way as expected. As voting on posts that don't exist should never work, we will simply call `canVote()` on a post id that doesn't exist. This test is pretty straight forward:
|
||||
|
||||
```
|
||||
it('should not be able to vote in an unexisting post', async () => {
|
||||
const userCanVote = await DReddit.methods.canVote("123").call();
|
||||
assert.equal(userCanVote, false);
|
||||
});
|
||||
```
|
||||
|
||||
We also want to make sure that `canVote()` resolves to `true` in case a user can indeed vote a certain post. We can again reuse the `postId` that we've stored earlier:
|
||||
|
||||
```
|
||||
it('should be able to vote in a post if account has not voted before', async () => {
|
||||
const userCanVote = await DReddit.methods.canVote(postId).call();
|
||||
assert.equal(userCanVote, true);
|
||||
});
|
||||
```
|
||||
|
||||
Wonderful, we have 5 passing tests now!
|
||||
|
||||
## Testing `vote()`
|
||||
|
||||
Of course we want to test whether one of our application's core features works as well. There's certainly different ways to verify whether `vote()` does what it's supposed to do, but for this tutorial we'll simply check whether the owner account of the vote emitted by the `NewVote` event is the same as the account that performed the vote. We can actually take some inspiration from our previous tests:
|
||||
|
||||
```
|
||||
it("should be able to vote in a post", async () => {
|
||||
const receipt = await DReddit.methods.vote(postId, 1).send();
|
||||
const Vote = receipt.events.NewVote;
|
||||
assert.equal(Vote.returnValues.owner, accounts[0]);
|
||||
});
|
||||
```
|
||||
|
||||
## Test that only one vote per post is allowed
|
||||
|
||||
The last but essential functionality we want to test is that whether our Smart Contract allows users to vote multiple times on the same post, which for obvious reasons shouldn't be possible. Using the `async/await` syntax we can test this very nicely by adding a `try/catch` block. When a user votes on a post she has already voted on, `vote()` will fail in which case we can make our assertions accordingly:
|
||||
|
||||
```
|
||||
it('should not be able to vote twice', async () => {
|
||||
try {
|
||||
const receipt = await DReddit.methods.vote(postId, 1).send();
|
||||
assert.fail('should have reverted');
|
||||
} catch (error){
|
||||
assert(error.message.search('revert') > -1, 'Revert should happen');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This might look a bit confusing first but it's actually pretty straight forward. In case `vote()` fails, we should not reach the `assert.fail()` call but end up in the `catch()` block immediately. If that was not the case, the test would fail. This is a very common pattern when testing negatives.
|
||||
|
||||
Okay, one last time we run `embark test` and if the output looks like the following, we're fully covered in terms of tests!
|
||||
|
||||
|
||||
```
|
||||
❯ embark test
|
||||
Compiling contracts
|
||||
|
||||
|
||||
DReddit
|
||||
✓ should work (0ms) - [0 gas]
|
||||
✓ should be able to create a post and receive it via contract event (60ms) - [160689 gas]
|
||||
✓ post should have correct data (18ms) - [0 gas]
|
||||
✓ should not be able to vote in an unexisting post (14ms) - [0 gas]
|
||||
✓ should be able to vote in a post if account hasn't voted before (12ms) - [0 gas]
|
||||
✓ should be able to vote in a post (42ms) - [65115 gas]
|
||||
✓ shouldn't be able to vote twice (37ms) - [22815 gas]
|
||||
|
||||
|
||||
7 passing (5s) - [Total: 3130955 gas]
|
||||
|
||||
> All tests passed
|
||||
```
|
||||
|
||||
Awesome! If you run into any issues, check out the repository with all steps recorded [here](https://github.com/embark-framework/dreddit-tutorial). In [the next and last part of this series](/news/2019/02/18/building-a-decentralized-reddit-with-embark-part-3/), we'll be building a front-end for our DReddit app using React. Until then, feel free to add more tests as you like!
|
|
@ -0,0 +1,874 @@
|
|||
title: Building a decentralized Reddit with Embark - Part 3
|
||||
summary: "In this third and last part of the tutorial series about building a decentralized Reddit with Embark, we're building the front-end for our application using React and EmbarkJS."
|
||||
categories:
|
||||
- tutorials
|
||||
layout: blog-post
|
||||
author: pascal_precht
|
||||
---
|
||||
|
||||
Hopefully you've read [the first](/news/2019/02/04/building-a-decentralized-reddit-with-embark-part-1/) and [second part](/news/2019/02/11/building-a-decentralized-reddit-with-embark-part-2/) of this tutorial on building a decentralized Reddit application using Embark. If not, we highly recommend you doing so, because in this part, we'll be focussing on building the front-end for our application and continue where we've left off.
|
||||
|
||||
- [**Part 1** - Setting up the project and implementing a Smart Contract](/news/2019/02/04/building-a-decentralized-reddit-with-embark-part-1/)
|
||||
- [**Part 2** - Testing the Smart Contract through EmbarkJS](/news/2019/02/11/building-a-decentralized-reddit-with-embark-part-2/)
|
||||
|
||||
We'll be using React as a client-side JavaScript library to build our application. However, we can use any framework of our choice, so feel free to follow along while using your favourite framework equivalents!
|
||||
|
||||
**The code for this tutorial can be found in [this repository](https://github.com/embark-framework/dreddit-tutorial)**.
|
||||
|
||||
## Rendering our first component
|
||||
|
||||
Alright, before we jump straight into building components that will talk to our Smart Contract instance, let's first actually render a simple text on the screen just to make sure our setup is working correctly.
|
||||
|
||||
For that, what we'll do is adding React as a dependency to our project. In fact, we'll be relying on two packages - `react` and `react-dom`. The latter is needed to render components defined with React in a DOM environment, which is what a Browser essentially is.
|
||||
|
||||
Let's add the following `dependencies` section to our projects `package.json`:
|
||||
|
||||
```
|
||||
"dependencies": {
|
||||
"react": "^16.4.2",
|
||||
"react-dom": "^16.4.2"
|
||||
}
|
||||
```
|
||||
|
||||
Once that is done we need to actually install those dependencies. For that we simply execute the following command in our terminal of choice:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Now we can go ahead and actually make use of React. As Embark is framework agnostic, we won't be focussing too much on details specific to React, just the least amount that is needed to make our app work.
|
||||
|
||||
Creating components in React is pretty straight forward. All we need to do is creating a class that extends React's `Component` type and add a `render()` method that will render the component's view.
|
||||
|
||||
Let's create a folder for all of our components inside our projects:
|
||||
|
||||
```
|
||||
$ mkdir app/js/components
|
||||
```
|
||||
|
||||
Next, we create a file for our root component. We call it simply `App` and use the same file name:
|
||||
|
||||
```
|
||||
$ touch app/js/components/App.js
|
||||
```
|
||||
|
||||
Alright, as mentioned earlier, we really just want to render some text on the screen for starters. Here's what that could look like:
|
||||
|
||||
```
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class App extends Component {
|
||||
|
||||
render() {
|
||||
return <h1>DReddit</h1>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is probably self explanatory, but all we're doing here is importing `React` and its `Component` type and create an `App` class that extends `Component`. The `render()` method will be used by React to render the component's view and has to return a template that is written in JSX syntax. JSX looks a lot like HTML just that it comes with extra syntax to embed things like control structures. We'll make use of that later!
|
||||
|
||||
Okay now that we have this component defined, we need to tell React to actually render this particular component. For that, we head over to `app/js/index.js` and add the following code:
|
||||
|
||||
```
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { App } from './components/App';
|
||||
|
||||
render(<App />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
We need to import `React` again as it has to be available in this script's scope. We also import a `render` function from `react-dom`, which is used to render our root component (`App`) into some element inside our HTML document. In this case we say that the element in which we want to render our root component is the element with the id `root`.
|
||||
|
||||
Let's set this up really quick. In `app/index.html` add a new element with a `root` id:
|
||||
|
||||
```
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
```
|
||||
|
||||
Notice that we've also moved the `script` tag inside the body tag, after the element with the `root` id. This is just one way to work around the fact that the element we're referencing inside our `render()` method is actually available in the document at the time the script is executed.
|
||||
|
||||
That should do it! Let's spin up Embark, we should then see our component rendered on the screen:
|
||||
|
||||
```
|
||||
$ embark run
|
||||
```
|
||||
|
||||
## Building a `CreatePost` component
|
||||
|
||||
Alright, enough warm up. Time to build components that are useful. We start off with building a component that lets users create posts through our application. Similar to `App`, we'll introduce a new component `createPost` that comes with a `render()` method to display a simple form for entering data. We'll also need to add event handlers to the form so that when a user submits the form, we can actually access the data and later on send it to our Smart Contract.
|
||||
|
||||
Creating a simple form is very straight forward:
|
||||
|
||||
```
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class CreatePost extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form>
|
||||
<div>
|
||||
<label>Topic</label>
|
||||
<input type="text" name="topic" />
|
||||
</div>
|
||||
<div>
|
||||
<textarea name="content"></textarea>
|
||||
</div>
|
||||
<button>Post</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To actually render this component on screen, we need to make it part of our `App` component. Or, to be more specific, have the `App` component render our `CreatePost` component. For now we can simply add it to `App`'s render function like this;
|
||||
|
||||
|
||||
```
|
||||
import { CreatePost } from './CreatePost';
|
||||
|
||||
export class App extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1>DReddit</h1>
|
||||
<CreatePost />
|
||||
</React.Fragment&>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
React doesn't allow for multiple root elements in a single component's view, so we have to take advantage of `React.Fragment`. Obviously, there's not too much going on here apart from us rendering a static form. Also notice that we don't spend too much time and effort on making the form look nice as we focus on the functionality for now. Consider that homework!
|
||||
|
||||
Let's make this form functional. First of all we want make sure that data entered into the form is available inside our component. React components maintain an object called `state` that can be used for exactly that. All we have to do is to initialize it with some initial values and update it using a `setState()` method if needed.
|
||||
|
||||
Let's introduce `state` in our component by adding a constructor and initializing it accordingly:
|
||||
|
||||
```
|
||||
export class CreatePost extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
topic: '',
|
||||
content: '',
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Next we bind that state to our form fields:
|
||||
|
||||
```
|
||||
<form>
|
||||
<div>
|
||||
<label>Topic</label>
|
||||
<input type="text" name="topic" value={this.state.topic} />
|
||||
</div>
|
||||
<div>
|
||||
<textarea name="content" value={this.state.content}></textarea>
|
||||
</div>
|
||||
<button>Post</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
No worries, we'll make use of `loading` in a second. Last but not least we want to add some event handlers so that changes in the view will be reflected back to our component's state as the user is entering data. To make sure everything works fine, we'll also add an event handler for the form submission and output the data in `state`. Here's what our `handleChange()` and `createPost()` handlers looks like:
|
||||
|
||||
```
|
||||
export class CreatePost extends Component {
|
||||
...
|
||||
handleChange(field, event) {
|
||||
this.setState({
|
||||
[field]: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
createPost(event) {
|
||||
event.preventDefault();
|
||||
console.log(this.state);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Notice how we're using `setState()` inside `handleChange()` to update whatever field name has been passed to that method. Now all we need to do is attach those handlers to our form:
|
||||
|
||||
```
|
||||
<form onSubmit={e => createPost(e)}>
|
||||
<div>
|
||||
<label>Topic</label>
|
||||
<input
|
||||
type="text"
|
||||
name="topic"
|
||||
value={this.state.topic}
|
||||
onChange={e => handleChange('topic', e)} />
|
||||
</div>
|
||||
<div>
|
||||
<textarea
|
||||
name="content"
|
||||
value={this.state.content}
|
||||
onChange={e => handleChange('content', e})></textarea>
|
||||
</div>
|
||||
<button type="submit">Post</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
Since we're using the `onSubmit()` handler of the form, it's also important that we either add a `type="submit"` to our `button` or change the button to an `<input type="submit">` element. Otherwise, the form won't emit a submit event.
|
||||
|
||||
Nice! With that in place, we should see the component's `state` in the console when submitting the form! The next challenge is to use `EmbarkJS` and its APIs to make our component talk to our Smart Contract instance.
|
||||
|
||||
### Uploading data to IPFS
|
||||
|
||||
Recall from our [first part](/news/2019/02/04/building-a-decentralized-reddit-with-embark-part-1/#Creating-posts) of this tutorial that our `DReddit` Smart Contract comes with a `createPost()` method that takes some bytes as post data. Those bytes are actually not the post data itself, but an IPFS hash that points to the post data. In other words, we'll have to somehow create such a hash and make sure the data is uploaded to IPFS as well.
|
||||
|
||||
Luckily, EmbarkJS comes with plenty of convenient APIs to do exactly that! `EmbarkJS.Storage.saveText()` takes a string, uploads it to IPFS and returns its hash which can then be used to create a post using our Smart Contract. One thing to keep in mind is that those APIs are asynchronous. Similar to how we wrote tests in [part two](/news/2019/02/11/building-a-decentralized-reddit-with-embark-part-2/#Testing-createPost) of this tutorial, we'll use `async/await` to write asynchronous code in a synchronous fashion.
|
||||
|
||||
```
|
||||
async createPost(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
||||
const ipfsHash = await EmbarkJS.Storage.saveText(JSON.stringify({
|
||||
topic: this.state.topic,
|
||||
content: this.state.content
|
||||
}));
|
||||
|
||||
this.setState({
|
||||
topic: '',
|
||||
content: '',
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
We use `JSON.stringify()` on an object that holds the `topic` and `content` of the post to be created. This is also the first time we put `loading` into action. Setting it to `true` before, and `false` after we've performed our operations lets us render a useful message as the user is waiting for updates.
|
||||
|
||||
```
|
||||
<form onSubmit={e => createPost(e)}>
|
||||
...
|
||||
{this.state.loading &&
|
||||
<p>Posting...</p>
|
||||
}
|
||||
</form>
|
||||
```
|
||||
|
||||
Obviously, we're not done yet though. All we do right now is uploading the post's data to IPFS and receiving the hash, but we still need to take that hash and send it to our Smart Contract using its `createPost()` method. Let's do that!
|
||||
|
||||
### Sending transactions to create posts
|
||||
|
||||
To send a transaction to our Smart Contract, we can again take advantage of EmbarkJS' APIs, similar to how we did it in the [second part](/news/2019/02/11/building-a-decentralized-reddit-with-embark-part-2). We also need to get hold of an Ethereum account to send the transaction from. This will be very straight forward as we'll be just relying on the accounts that are generated by the Ethereum node that Embark spins up for us.
|
||||
|
||||
Once we have those things in place we can get a gas estimation for our transaction and send the data over. Here's how we retrieve our accounts, notice that `async/await` can be used here as well:
|
||||
|
||||
```
|
||||
async createPost(event) {
|
||||
...
|
||||
const accounts = await web3.eth.getAccounts();
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Next up we'll import a `DReddit` Smart Contract instance from EmbarkJS and use it to get a gas estimation from `web3`. We can then use the estimation and one of our accounts to actually send the transaction:
|
||||
|
||||
```
|
||||
import DReddit from './artifacts/contracts/DReddit';
|
||||
...
|
||||
|
||||
async createPost(event) {
|
||||
...
|
||||
const accounts = await web3.eth.getAccounts();
|
||||
const createPost = DReddit.methods.createPost(web3.utils.toHex(ipfsHash));
|
||||
const estimate = await createPost.estimateGas();
|
||||
|
||||
await createPost.send({from: accounts[0], gas: estimate});
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Sweet, with that, our `createPost` method is done! We haven't built a list of all created posts yet, but if we open up the app and create a post, we can use Embark to double check whether the transaction went through successfully. Simply watch the output in the terminal after running `embark run`. We should see a confirmation that looks something like this:
|
||||
|
||||
```
|
||||
Blockchain> DReddit.createPost("0x516d5452427a47415153504552614645534173335133765a6b59436633634143776368626263387575623434374e") | 0xbbeb9fa1eb4e3434c08b31409c137c2129de65eb335855620574c537b3004f29 | gas:136089 | blk:18455 | status:0x1
|
||||
```
|
||||
|
||||
## Creating a Post component
|
||||
|
||||
The next challenge lies in fetching all created posts from our Smart Contract and IPFS so we can render them on screen. We start simple and first create a new component that will render a single post. After that we'll look into rendering a list of posts dynamically, based on the data we're fetching.
|
||||
|
||||
Again, our application won't look particularly pretty, we'll just focus on getting the core functionality right. A post component needs to render the post topic, its content, the owner of the post, ideally the date when it has been created, and a button to up and down vote respectively.
|
||||
|
||||
Here's what such a component with a basic template could look like:
|
||||
|
||||
```
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Post extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<h3>Some Topic</h3>
|
||||
<p>This is the content of a post</p>
|
||||
<p><small><i>created at 2019-02-18 by 0x00000000000000</i></small></p>
|
||||
<button>Upvote</button>
|
||||
<button>Downvote</button>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are different ways to make the data being rendered dynamic. Usually, we would probably pass a one or more properties to the `Post` component that represents the entire post object and can then be displayed inside its `render()` method. However, for this tutorial we're going to choose a slightly different path. We'll make `Post` receive IPFS hash that's stored in the Smart Contract and have it resolve the data itself.
|
||||
|
||||
Let's stay consistent with our naming and say the property we're expecting to be filled with data is called `description`, just like the one used inside the Smart Contract. We can then use `EmbarkJS.Storage.get()` with the IPFS hash to fetch the data that represents the actual post. In order to render the data inside `Post`'s view, we'll parse it and use `setState()` accordingly.
|
||||
|
||||
To make sure all of that happens once the component is ready to do its work, we'll do all of that inside its `componentDidMount()` life cycle hook:
|
||||
|
||||
```
|
||||
import React, { Component } from 'react';
|
||||
import EmbarkJS from '.artifacts/embarkjs';
|
||||
|
||||
export class Post extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
topic: '',
|
||||
content: ''
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const ipfsHash = web3.utils.toAscii(this.props.description);
|
||||
const data = await EmbarkJS.Storage.get(ipfsHash);
|
||||
const { topic, content } = JSON.parse(data);
|
||||
|
||||
this.setState({ topic, content });
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
There's one gotcha to keep in mind here: Calling `EmbarkJS.Storage.get()` or any `EmbarkJS` function on page load can fail, because the storage system might not be fully initialized yet. This wasn't a problem for the previous `EmbarkJS.Storage.uploadText()` because we called that function well after Embark had finished initializing
|
||||
|
||||
Theoretically however, there could be a race condition even for creating a post. To ensure that EmbarkJS is ready at any point in time, we use its `onReady()` hook. `EmbarkJS.onReady()` takes a callback which will be executed once EmbarkJS is ready to go. The best place to do this in our app is probably where we attempt to render our application, so let's wrap that `render()` call in our `App` component inside Embark's `onReady()` function.
|
||||
|
||||
```
|
||||
EmbarkJS.onReady(() => {
|
||||
render(<App />, document.getElementById('root'));
|
||||
});
|
||||
```
|
||||
|
||||
This also means our app will only render when EmbarkJS is ready, which theoretically could take a little longer. However in this tutorial, chances are very low this is becoming a problem.
|
||||
|
||||
Let's also quickly add the `owner` and creation date. The `owner` is expected to be passed down as a property. The same goes for the creation date. We just need to make sure it'll be formatted in a way the users can make sense of the data. We'll use the `dateformat` library for that and install it as a dependency like this:
|
||||
|
||||
```
|
||||
$ npm install --save dateformat
|
||||
```
|
||||
|
||||
Once that is done, we can update our `Post` component's `render()` function to calculate a properly formatted date based on the `creationDate` that has been passed down through properties:
|
||||
|
||||
```
|
||||
...
|
||||
import dateformat from 'dateformat';
|
||||
|
||||
export class Post extends Component {
|
||||
...
|
||||
render() {
|
||||
const formattedDate = dateformat(
|
||||
new Date(this.props.creationDate * 1000),
|
||||
'yyyy-mm-dd HH:MM:ss'
|
||||
);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<h3>{this.state.topic}</h3>
|
||||
<p>{this.state.content}</p>
|
||||
<p><small><i>created at {formattedDate} by {this.props.owner}</i></small></p>
|
||||
<button>Upvote</button>
|
||||
<button>Downvote</button>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that variables created inside `render()` can be interpolated as they are - there's no need to make them available on `props` or `state`. As a matter of fact, `props` are always considered read only in React.
|
||||
|
||||
Let's try out our new `Post` component with some static data by adding it to our `App` component's view. Next up, we'll make this dynamic by fetching the posts from our Smart Contract.
|
||||
|
||||
**Attention**: The hash used in this snippet might not be available in your local IPFS node, so you'll have to get hold of your own hash. This can be down by logging out the hash that is returned from IPFS and convert it to hex code.
|
||||
|
||||
```
|
||||
export class App extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1>DReddit</h1>
|
||||
<CreatePost />
|
||||
<Post
|
||||
description="0x516d655338444b53464546725369656a747751426d683377626b56707566335770636e4c715978726b516e4b5250"
|
||||
creationDate="1550073772"
|
||||
owner="0x00000000000"
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a List component
|
||||
|
||||
Before we can move on with building a component that renders a list of posts, we'll have to extend our Smart Contract with one more method. Since there's no canonical way to fetch array data from a Smart Contract, we'll be fetching the post data for each post one by one. We do that by first fetching the total number of posts and use that number to iterate over the available indices, which we can then use to fetch the actual posts.
|
||||
|
||||
Let's introduce a method `numPosts()` in our `DReddit` Smart Contract:
|
||||
|
||||
```
|
||||
function numPosts() public view returns (uint) {
|
||||
return posts.length;
|
||||
}
|
||||
```
|
||||
|
||||
`posts.length` will increase as we're adding posts, so it will always be the single source of truth when it comes to determining indices of posts. This would be a good opportunity to write another test - we'll leave that up to you!
|
||||
|
||||
With that in place, we can start building a new `List` component. The `List` component maintains a list of posts to render on screen, so we can start simple again and introduce the bare minimum like this:
|
||||
|
||||
```
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class List extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
posts: []
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<React.Fragment>
|
||||
{this.state.posts.map(post => {
|
||||
return (
|
||||
<Post
|
||||
key={post.id}
|
||||
description={post.description}
|
||||
creationDate={post.creationDate}
|
||||
owner={post.owner}
|
||||
/>)
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The most interesting part here is probably the `render()` method, in which we iterate over all `state.posts` (which at the moment is empty) and then render a `Post` component for every iteration. Another thing to note is that every `Post` receives a `key`. This is required in React when creating views from loops. We've never introduced a `post.id` in this tutorial, but don't worry, we'll fix that in a moment.
|
||||
|
||||
We can already put that in our `App` component. It won't render anything as we haven't fetched any posts yet, but that's what we'll do next.
|
||||
|
||||
|
||||
```
|
||||
import { List } from './List';
|
||||
|
||||
export class App extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1>DReddit</h1>
|
||||
<CreatePost />
|
||||
<List />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fetching posts data
|
||||
|
||||
Let's fill our new `List` component with life! As mentioned earlier, we'll use our Smart Contract's `numPosts()` method to get hold of the total number of posts available. We then use that number to iterate over all indices and request every post individually. Since this is logic we want to execute once the `List` component is ready, we'll use its `componentDidMount()` method for that:
|
||||
|
||||
```
|
||||
export class List extends Component {
|
||||
...
|
||||
async componentDidMount() {
|
||||
const totalPosts = await DReddit.methods.numPosts().call();
|
||||
|
||||
let list = [];
|
||||
|
||||
for (let i = 0; i < totalPosts; i++) {
|
||||
const post = DReddit.methods.posts(i).call();
|
||||
list.push(post);
|
||||
}
|
||||
|
||||
list = await Promise.all(list);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Notice that in the above code we don't `await` the calls to every individual post. This is on purpose as we don't want to wait on each and every promise to resolve, but first collect all of the promises we need and then resolve them all in one go using `Promise.all().`
|
||||
|
||||
Last but not least, we need to add an `id` property to every post as mentioned earlier. This is easily done by simply iterating over all posts and assigning the post's index as `id`. Once that is done, we can use `setState()` to update our component's state and render the list:
|
||||
|
||||
```
|
||||
async componentDidMount() {
|
||||
...
|
||||
list = list.map((post, index) => {
|
||||
post.id = index;
|
||||
return post;
|
||||
});
|
||||
|
||||
this.setState({ posts: list });
|
||||
}
|
||||
```
|
||||
|
||||
That's it! Our application now renders a list of all created posts. Unfortunately, posts are not being re-fetched automatically when adding new posts. For the time being, we'll have to reload the browser every time after adding a post. However, this we'll address now.
|
||||
|
||||
### Reloading posts
|
||||
|
||||
There is certainly different ways to make the list of posts update automatically, so take the following approach with a grain of salt. What we need is a way to have the `createPost` component tell the `List` component to reload its posts. However, there's no communication layer in place when building a simple React app like this, so the most straight forward way to make this possible, is to move the logic of loading the posts in the parent component of `CreatePost` and `List` (in our case `App`), and have it pass that logic down to places where its needed. This also means we'll be fetching the list inside `App` and pass down the pure data to `List`.
|
||||
|
||||
If this sounds overwhelming, no worries, it's more trivial than that! Let's start by introducing a `loadPosts()` function in our `App` component. Essentially we're moving everything from `List`'s `componentDidMount()` function into `App`:
|
||||
|
||||
```
|
||||
export class App extends Component {
|
||||
...
|
||||
async loadPosts() {
|
||||
const totalPosts = await DReddit.methods.numPosts().call();
|
||||
|
||||
let list = [];
|
||||
|
||||
if (totalPosts > 0) {
|
||||
for (let i = 0; i < totalPosts; i++) {
|
||||
const post = DReddit.methods.posts(i).call();
|
||||
list.push(post);
|
||||
}
|
||||
}
|
||||
|
||||
list = await Promise.all(list);
|
||||
list = list.map((post, index) => {
|
||||
post.id = index;
|
||||
return post;
|
||||
});
|
||||
|
||||
list;
|
||||
|
||||
this.setState({ posts: list });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To make this work we also need to introduce a `state` with the dedicated `posts`. After that, we make sure `loadPosts()` is called when `App` is mounted:
|
||||
|
||||
```
|
||||
export class App extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
posts: []
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await this.loadPosts();
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Last but not least, all we have to do is to pass the `posts` down to `List` and `loadPosts()` to `CreatePost` as a callback handler if you will:
|
||||
|
||||
```
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1>DReddit</h1>
|
||||
<CreatePost afterPostHandler={this.loadPosts.bind(this)}/>
|
||||
<List posts={this.state.posts}/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Once that is done, we can consume `posts` and `afterPostHandler()` from `this.props` respectively. In `List`'s `render()` function we'll do (notice we don't rely on `this.state` anymore):
|
||||
|
||||
```
|
||||
render() {
|
||||
return (<React.Fragment>
|
||||
{this.props.posts.map(post => {
|
||||
...
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
And in `CreatePost` we call `afterPostHandler()` after a post has been created:
|
||||
|
||||
```
|
||||
async createPost(event) {
|
||||
...
|
||||
await createPost.send({from: accounts[0], gas: estimate});
|
||||
await this.props.afterPostHandler();
|
||||
|
||||
this.setState({
|
||||
topic: '',
|
||||
content: '',
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Wonderful! The list now automatically reloads after creating posts, give it a try!
|
||||
|
||||
## Add voting functionality
|
||||
|
||||
The final feature we'll be implementing is the up and down voting of posts. This is where we come back to our `Post` component that we've created earlier. In order to make this feature complete we'll have to:
|
||||
|
||||
- Render the number of up and down votes per post
|
||||
- Add handlers for users to up and down vote
|
||||
- Determine if a user can vote on a post
|
||||
|
||||
### Rendering number of votes
|
||||
Let's start with the first one, as it's the most trivial one. While the number of up and down votes is already attached to the data that we receive from our `DReddit` Smart Contract, it's not yet in the right format as it comes back as a string. Let's make sure we parse the up and down vote counts on posts by extending our `App`'s `loadPosts()` method like this:
|
||||
|
||||
```
|
||||
async loadPosts() {
|
||||
...
|
||||
list = list.map((post, index) => {
|
||||
post.id = index;
|
||||
post.upvotes = parseInt(post.upvotes, 10);
|
||||
post.downvotes = parseInt(post.downvotes, 10);
|
||||
return post;
|
||||
});
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Once that is done we can pass each post's `upvotes` and `downvotes` to every `Post` component via its `props` inside our `List` component:
|
||||
|
||||
```
|
||||
export class List extends Component {
|
||||
...
|
||||
render() {
|
||||
return (<React.Fragment>
|
||||
{this.props.posts.map(post => {
|
||||
return (<Post
|
||||
key={post.id}
|
||||
description={post.description}
|
||||
creationDate={post.creationDate}
|
||||
upvotes={post.upvotes}
|
||||
downvotes={post.downvotes}
|
||||
owner={post.owner}
|
||||
/>)
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Rendering the number of `upvotes` and `downvotes` is then really just a matter of interpolating them in `Post`'s `render()` function. We're just going to add them next to the buttons, but feel free to put them somewhere else:
|
||||
|
||||
```
|
||||
export class Post extends Component {
|
||||
...
|
||||
render() {
|
||||
...
|
||||
return (
|
||||
<React.Fragment>
|
||||
...
|
||||
{this.props.upvotes} <button>Upvote</button>
|
||||
{this.props.downvotes} <button>Downvote</button>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implement up and down votes
|
||||
|
||||
Similar to when creating new posts, making the up and down vote buttons work requires sending transactions to our `DReddit` Smart Contract. So we'll do almost the same thing as in our `CreatePost` component, just that we're calling the Smart Contract's `vote()` method. If you recall, the `vote()` method takes a post id and the vote type, which is either `NONE`, `UPVOTE` or `DOWNVOTE` and are stored as `uint8`.
|
||||
|
||||
It makes sense to introduce the same representation in our app so we can use descriptive names, but rely on uint values at the same time. There are no enum data structures in JavaScript so we'll use a hash object instead:
|
||||
|
||||
```
|
||||
const BALLOT = {
|
||||
NONE: 0,
|
||||
UPVOTE: 1,
|
||||
DOWNVOTE: 2
|
||||
}
|
||||
```
|
||||
|
||||
We don't actually have the post id available in our `Post` component yet. That's easily added in our `List` component, by now you should know how to do that!
|
||||
|
||||
We can then add click handlers to our up and down vote buttons and pass one of the `BALLOT` types to them (notice that we added `BALLOT.NONE` only for completeness-sake but don't actually use it in our code):
|
||||
|
||||
```
|
||||
<button onClick={e => this.vote(BALLOT.UPVOTE)}>Upvote</button>
|
||||
<button onClick={e => this.vote(BALLOT.DOWNVOTE)}>Downvote</button>
|
||||
```
|
||||
|
||||
The next thing we need to do is sending that vote type along with the post id to our Smart Contract:
|
||||
|
||||
```
|
||||
async vote(ballot) {
|
||||
const accounts = await web3.eth.getAccounts();
|
||||
const vote = DReddit.methods.vote(this.props.id, ballot);
|
||||
const estimate = await vote.estimateGas();
|
||||
|
||||
await vote.send({from: accounts[0], gas: estimate});
|
||||
}
|
||||
```
|
||||
|
||||
Obviously, we also want to update the view when a vote has been successfully sent. Right now we're reading a post's up and down votes from its `props` and render them accordingly. However, we want to update those values as votes are coming in. For that we'll change our code to only read the up and down votes from `props` once and store them in the component's state.
|
||||
|
||||
```
|
||||
export class Post extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
topic: '',
|
||||
content: '',
|
||||
upvotes: this.props.upvotes,
|
||||
downvotes: this.props.downvotes
|
||||
};
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
We also change the component's view to render the values from state instead of `props`:
|
||||
|
||||
```
|
||||
render() {
|
||||
...
|
||||
return (
|
||||
<React.Fragment>
|
||||
...
|
||||
{this.state.upvotes} <button ...>Upvote</button>
|
||||
{this.state.downvotes} <button ...>Downvote</button>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
After that we can update the state with new votes using `setState()`, right after a vote has been sent:
|
||||
|
||||
```
|
||||
async vote(ballot) {
|
||||
...
|
||||
this.setState({
|
||||
upvotes: this.state.upvotes + (ballot == BALLOT.UPVOTE ? 1 : 0),
|
||||
downvotes: this.state.downvotes + (ballot == BALLOT.DOWNVOTE ? 1 : 0)
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**That's it!** We can now up and down vote on posts...but only once! Yes, that's right. When we try to vote multiple times on the same post, we'll actually receive an error. That's because, if you remember, there's a restriction in our Smart Contract that makes sure users can not vote on posts that they've either already voted on, or created themselves.
|
||||
|
||||
Let's make sure this is reflected in our application's UI and wrap up this tutorial!
|
||||
|
||||
### Use `canVote()` to disable vote buttons
|
||||
|
||||
We'll keep this one very simple - if a user cannot vote on a post, the voting buttons should be simply disabled. We can easily determine whether a user is allowed to vote by calling our Smart Contract's `canVote()` method. Another thing we need to consider is that we shouldn't allow a user to vote when a vote for the same post is already in flight but hasn't completed yet.
|
||||
|
||||
Let's introduce a new state properties for that first. In general we can say that a user is allowed to vote, and that she is not submitting a vote in this very moment:
|
||||
|
||||
```
|
||||
export class Post extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
topic: '',
|
||||
content: '',
|
||||
upvotes: this.props.upvotes,
|
||||
downvotes: this.props.downvotes,
|
||||
canVote: true,
|
||||
submitting: false
|
||||
};
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Next, we update our `Post` component's `render()` function to disable the voting buttons if a vote is in flight, or a user is simply not allowed to vote:
|
||||
|
||||
```
|
||||
render() {
|
||||
...
|
||||
const disabled = this.state.submitting || !this.state.canVote;
|
||||
return (
|
||||
<React.Fragment>
|
||||
...
|
||||
{this.state.upvotes} <button disabled={disabled} ...>Upvote</button>
|
||||
{this.state.downvotes} <button disabled={disabled} ...>Downvote</button>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Last but not least, we have to make sure the state properties are updated accordingly. We'll call our Smart Contract's `canVote()` method when a post is initialized:
|
||||
|
||||
```
|
||||
export class Post extends Component {
|
||||
...
|
||||
async componentDidMount() {
|
||||
...
|
||||
const canVote = await DReddit.methods.canVote(this.props.id).call();
|
||||
this.setState({ topic, content, canVote });
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
And when a vote is being made, we set `submitting` to `true` right before we send a transaction and set it back to `false` again when the transaction is done. At this point, we also know that a vote has been made on this post, so `canVote` can be set to `false` at the same time:
|
||||
|
||||
```
|
||||
async vote(ballot) {
|
||||
...
|
||||
this.setState({ submitting: true });
|
||||
await vote.send({from: accounts[0], gas: estimate + 1000});
|
||||
|
||||
this.setState({
|
||||
...
|
||||
canVote: false,
|
||||
submitting: false
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**And we're done!**
|
||||
|
||||
## Wrapping it up
|
||||
|
||||
Congratulations! You've completed the tutorial on building a simple decentralized Reddit application! You might have noticed that this is only the tip of the iceberg though, as there are so many things that can be done to improve and optimize this application. Here are some ideas for further exploration:
|
||||
|
||||
- Sort the posts in reversed chronological order so that the latest post is always on top
|
||||
- Rely on Smart Contracts Events to reload list
|
||||
- Introduce routing so there can be different views for creating and viewing posts
|
||||
- Use CSS to make the application look nice
|
||||
|
||||
We hope you've learned that it's not too hard to build a DApp that uses IPFS and talks to Smart Contracts, and also how Embark can help you doing all of these things.
|
||||
|
||||
**We've recorded every single step of this tutorial [in this repository](https://github.com/embark-framework/dreddit-tutorial)**, so feel free to go ahead, clone it, play with it, compare it with your work or change it to your needs. There will be more tutorials of this kind in the future, so make sure to [follow us on Twitter](https://twitter.com/EmbarkProject) as well for updates!
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
title: How to upgrade to Embark 4
|
||||
summary: "In this guide, we'll learn how to upgrade a Dapp created with Embark 3.x to Embark 4"
|
||||
author: jonathan_rainville
|
||||
categories:
|
||||
- tutorials
|
||||
layout: blog-post
|
||||
---
|
||||
|
||||
The release of Embark 4.0 is close at hand and the release candidate, `beta.1`, will introduce some breaking changes. Let's see what it takes to update an Embark 3.x Dapp to Embark 4.
|
||||
|
||||
## Use **any** frontend build tool!
|
||||
|
||||
That's right! The use of Embark's builtin pipeline in no longer required.
|
||||
|
||||
Historically, Embark 3.x came with a special Webpack pipeline because it automated development tasks, such as enabling the use of "magic" imports (ie `import SimpleStorage from "Embark/contracts/SimpleStorage";` or `import EmbarkJS from Embark/EmbarkJS`), and establishing a Web3 connection for the Dapp.
|
||||
|
||||
However, we discovered the hard way that those advantages were not worth the hit in development efficiency, compared to using an optimized pipeline, such as `create-react-app` or Angular CLI. Indeed, on every save, Embark would regenerate a lot of the Dapp-side code and then webpack the entire Dapp, often taking quite some time.
|
||||
|
||||
Therefore, we are announcing that Embark 4 can use **any** frontend development build tooling, letting Embark handle the things that it does best. This means we can use tools such as `create-react-app` or Angular CLI, or pretty much any other tool of your choice, alongside Embark. The Embark 3.x pipeline is still available for use for quick start applications if needed.
|
||||
|
||||
To migrate an existing Embark 3.x Dapp over to use Embark 4 with a third party pipeline, there are few small changes to your Dapp that are needed.
|
||||
|
||||
{% notification info 'NOTE' %}
|
||||
If you are not interested in using a third party pipeline, you can skip to the next section to [see the rest of the breaking changes needed to migrate a Dapp to Embark 4](#New-Web3-plugin).
|
||||
{% endnotification %}
|
||||
|
||||
### Converting to another pipeline
|
||||
|
||||
Converting to a third party pipeline is easy. This can be done with three simple improvements that Embark 4 has made available for us.
|
||||
|
||||
#### Artifact generation directory
|
||||
|
||||
NOTE: If you are planning on using Embark's built-in Webpack pipeline (and not use a third party pipeline), please [skip down to the remainder of the Embark 4 breaking changes](#New-Web3-plugin).
|
||||
|
||||
Embark 4 generates [Smart Contract artifacts](/docs/javascript_usage.html#Embark-Artifacts) for all of the Smart Contract in your Dapp. These artifacts enable importing the Dapp's Smart Contracts into the Dapp's source code. Most of these artifacts were already generated before, but lived inside the `.embark/` folder. Since most modern frontend build systems require source files to live inside of a very specific source folder, we have given developers the opportunity to specify the destination folder for these artifacts, allowing the frontend build tool to pick them up for processing.
|
||||
|
||||
The first thing we need to do is add a new `generationDir` property in the root of `embark.json`. This property tells Embark where to place the generated artifacts in the Dapp's filesystem. For example, `create-react-app` (CRA) has `src/` as source folder and the artifacts must be placed in that folder, so we would add in `embark.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"generationDir": "src/embarkArtifacts"
|
||||
}
|
||||
```
|
||||
|
||||
#### "Magic" imports
|
||||
Afterwards, we need to convert all "magic" imports in our Dapp's code to relative imports.
|
||||
|
||||
The first one is the EmbarkJS import. The "magic" import is `"Embark/EmbarkJS"`. Anywhere we have `"Embark/EmbarkJS"` in our Dapp's code, we need to convert that to the relative path. Because we are trying to get the `EmbarkJS` library, and the `embarkjs.js` script is located in the root of `embarkArtifacts/`, we need to replace
|
||||
|
||||
```javascript
|
||||
import EmbarkJS from "Embark/EmbarkJS"
|
||||
```
|
||||
with
|
||||
```javascript
|
||||
import EmbarkJS from "./embarkArtifacts/embarkjs"
|
||||
```
|
||||
{% notification info 'NOTE' %}
|
||||
NOTE: The relative path is dependent upon the generationDir setting specified in embark.json [see the "Artifact generation directory" section above](#Artifact-generation-directory).
|
||||
{% endnotification %}
|
||||
|
||||
Secondly, we need to update the "magic" Smart Contract imports. These will need to change from
|
||||
|
||||
```javascript
|
||||
import ContractName from "Embark/contract/ContractName";
|
||||
```
|
||||
to
|
||||
```javascript
|
||||
import ContractName from "./embarkArtifacts/contracts/ContractName";
|
||||
```
|
||||
|
||||
Thirdly, there used to be `import web3 from "Embark/web3"`, but it has been removed in Embark 4 in favor of using a global Web3 object. Don't worry, Embark is not removing web3 support, far from it. We actually just got rid of an import that did not provide a lot of benefit. In Embark 4, the global `web3` object is now available everywhere in the Dapp.
|
||||
|
||||
Now, all the Embark files and configs from your Dapp can be moved in to a project created by the frontend build tool of your choice.
|
||||
|
||||
### New project with another pipeline
|
||||
|
||||
Starting a new Dapp from scratch is easy, we have two options.
|
||||
|
||||
#### Embark's create-react-dapp template
|
||||
|
||||
The easiest option is to use our [new Embark CRA template](https://github.com/embark-framework/embark-create-react-dapp-template). It sets up a simple Embark project with all of the familiar files present in an Embark 3.x Dapp, with one minor difference: the config files are located in an `embarkConfig/` folder in the root of the Dapp to make sure they don't clash with CRA's config folder/files.
|
||||
|
||||
To get started with Embark's CRA template,
|
||||
|
||||
```
|
||||
embark new --template embark-react-dapp my-dapp
|
||||
cd my-dapp
|
||||
embark run
|
||||
```
|
||||
|
||||
Then, in another terminal,
|
||||
|
||||
```
|
||||
cd my-dapp
|
||||
yarn start // or alternatively, npm run start
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
||||
#### For other build tools
|
||||
|
||||
If we want to use another build tool than CRA, here are the steps:
|
||||
|
||||
Create a project using a frontend build tool like Angular CLI. Then, in another directory, execute `embark new your_projects_name`.
|
||||
|
||||
Afterwards, we copy all the files and folders from the Embark project to the build tool's folder. The only tweak that you will need to do is go in `config/pipeline.js` and set `enabled: false`, so that Embark's pipeline is disabled.
|
||||
|
||||
We can also go in `embark.json` and remove the `app` section (as well as Embark's source dir that you will not be using).
|
||||
|
||||
Lastly, check out [the "Artifact generation directory" section above](#Artifact-generation-directory) to make sure your artifacts directory is set up correctly for you build tool.
|
||||
|
||||
There you go, your project is ready.
|
||||
|
||||
We know that these steps are a bit too much, so we are working on a new command that lets you initialize an Embark project from inside a build tool's directory. Keep an eye out for that.
|
||||
|
||||
## New Web3 plugin
|
||||
|
||||
Starting with Embark 4 beta.1, Embark no longer supplies the Dapp with `Web3.js` by default. Don't run. We did that so that we can now have the possibility of supporting more than just `Web3.js`, such as EthersJS, and more. You can even roll your own.
|
||||
|
||||
To continue using `Web3.js` inside the Embark 4 Dapp, execute the following command in the Embark console: `plugin install embarkjs-connector-web3`.
|
||||
|
||||
This simply [installs `embarkjs-connector-web3` as a plugin](https://embark.status.im/docs/installing_plugins.html). Alternatively, this plugin can be installed manually by executing:
|
||||
1. `yarn add embarkjs-connector-web3` or `npm install --save embarkjs-connector-web3`
|
||||
2. Adding `"embarkjs-connector-web3": {}` to the `plugins` section of `embark.json`
|
||||
|
||||
It's as simple as that. This plugin will add the necessary commands and code for the Dapp to connect to the blockchain and register the necessary providers. The only prerequisite is for the Dapp to import `EmbarkJS` at least once. If using a third party pipeline, the `EmbarkJS` file can be imported using `import EmbarkJS from "./embarkArtifacts/embarkjs.js"` (or as specified by the `generationDir` in `embark.json`). If using Embark's built-in pipeline, `EmbarkJS` can be imported using `import EmbarkJS from "Embark/EmbarkJS";`.
|
||||
|
||||
## New Blockchain account configs
|
||||
|
||||
Embark 4 adds some new blockchain account configurations. To try to keep things as simple as possible, these additions are really similar to the ones in the contract configuration. For more information, please read the [Accounts Blockchain configuration guide](https://embark.status.im/docs/blockchain_accounts_configuration.html) in our docs.
|
||||
|
||||
However, we did introduce some small breaking changes. We removed:
|
||||
- `account`: This is completely replaced by the new `accounts` property (notice the `s` at the end of `accounts`). It gives the developer more flexibility. To have exactly the same behavior as before, just use the `nodeAccounts` account type as [described in the docs](https://embark.status.im/docs/blockchain_accounts_configuration.md#parameter-descriptions)
|
||||
- `simulatorMnemonic`: Removed in favor of Ganache's default mnemonic. If this functionality is still needed, please specify the desired mnemonic in the [blockchain config's `mnemonic` account type](https://embark.status.im/docs/blockchain_accounts_configuration.md#parameter-descriptions).
|
||||
|
||||
## Conclusion
|
||||
|
||||
This is a small taste of the features added to Embark 4, namely the ability to use a frontend build tool of choice. However, Embark 4 is jam-packed with additional new features, which we'll detail during the Embark 4 release.
|
||||
|
||||
In the meantime, all the Embark 4 goodness doesn't come at too high a price in terms of breaking changes.
|
||||
|
||||
Upgrading to Embark 4 will be a blast. If you ever have an issue, make sure to hit us up on [Gitter](https://gitter.im/embark-framework/Lobby).
|
|
@ -0,0 +1,75 @@
|
|||
title: Introducing Embark 4.0 - Cockpit, Debugger and more
|
||||
summary: "Embark 4.0 is finally here! Check out what the greatest release yet has to offer!"
|
||||
author: jonny_zerah
|
||||
categories:
|
||||
- announcements
|
||||
layout: blog-post
|
||||
image: '/assets/images/EMBARK_HEADER_ALT_OPTIMIZED.jpg'
|
||||
---
|
||||
|
||||
![Embark](/assets/images/EMBARK_HEADER_ALT_OPTIMIZED.jpg "Embark")
|
||||
|
||||
**Embark 4.0 is officially out of beta and ready for developers around the world. Cockpit (the new web UI dashboard), a robust debugger, and the frontend-agnostic build pipeline provide the support you need to develop production-ready decentralized applications.**
|
||||
|
||||
2019 is off to a great start! We’ve been taking Embark 4.0 from alpha to beta, and are now happy to present the official release of version 4.0. It comes jam-packed with many new features, including Cockpit, a transaction debugger, and a massively improved integration with existing frontend tooling. To mark this major milestone, we’ve also launched our new website with updated docs, more tutorials, and a brand new look!
|
||||
|
||||
Thanks to all the developers who have been using, testing, contributing to, and providing feedback on the beta version. The official release of 4.0 is now ready for the world to use. Read on for an overview of the key features or simply get going with our [Quick Start Guide](/docs/quick_start.html).
|
||||
|
||||
{% notification info 'Embark now follows SemVer' %}
|
||||
Version 4.0 contains **some breaking changes**, however we kept them at a minimum and you can learn about all of them in our article on [upgrading DApps created with Embark 3.x](/news/2019/03/18/upgrading-to-embark-4/).
|
||||
|
||||
That said, with the release of 4.0 **Embark will now follow SemVer** making it easier for developers to update and watch out for changes.
|
||||
{% endnotification %}
|
||||
|
||||
## Cockpit – An intuitive Web Interface
|
||||
Cockpit has been under active development for a few months and is officially ready! Cockpit is your command center for building, debugging, and deploying decentralized applications.
|
||||
|
||||
**The dashboard** is the first page users see when they load Cockpit. It provides an overview of all processes controlled by Embark and comes with an interactive console and predictive commands, allowing developers to conveniently interact with Embark and all components (e.g. Ethereum blockchain, ENS, Whisper, IPFS/Swarm, etc). The dashboard also displays a summary of deployed contracts and enables users to search for accounts, blocks, addresses, and transactions.
|
||||
|
||||
For more information regarding Cockpit’s dashboard, please refer to the [Embark docs](/docs/cockpit_dashboard.html).
|
||||
|
||||
|
||||
![Cockpit Dashboard](/assets/images/cockpit_dashboard_release.png "Cockpit Dashboard")
|
||||
|
||||
**The blockchain explorer** provides detailed views of blocks, transactions, smart contracts, and connected accounts. We’ve also introduced a brand new way to analyze deployed instances of smart contracts. Within the contracts view, users can interact with a contract’s API, view the ABI and bytecode, retrieve the contract’s transaction logs, and invoke Cockpit’s new integrated debugger. [Learn more](/docs/cockpit_explorer.html)
|
||||
|
||||
|
||||
![Cockpit Explorer](/assets/images/cockpit_explorer_overview.png "Cockpit Explorer")
|
||||
|
||||
**Iterative Deployment** enables selective deployment of smart contracts to any network, removing headaches when it comes to complex applications. Using the deployment panel, single or multiple smart contracts can be deployed to production safely, with full control over the process. [Learn more](/docs/cockpit_deployment.html)
|
||||
|
||||
**The code editor** allows you to edit a DApp’s source files from within Cockpit for quick and easy updates. The web-based editor enables a DApp’s source code to be changed on the fly. Like any typical code editor, it has a file tree, can open multiple source files, and allows files to be added and deleted. Users can also access and interact with contact properties and methods in the editor’s UI. Contracts even get redeployed as changes are saved – iterative development at its best! [Learn more](/docs/cockpit_editor.html)
|
||||
|
||||
![Cockpit Editor](/assets/images/cockpit_editor_release.png "Cockpit Editor")
|
||||
|
||||
## Integrated Debugger
|
||||
Debugging is an important part of all software development and has been a significant challenge for blockchain developers for some time. The new Embark debugger provides an easy way to debug contracts by displaying solidity source codes lines where a transaction failed. This greatly speeds up development and helps to eliminate bugs.
|
||||
|
||||
The debugger comes in handy in a number of situations. For example, if a transaction fails, no problem! The debugger will spring into action and offer a quick shortcut to help identify the problem and start troubleshooting.
|
||||
|
||||
## Better tooling integration
|
||||
Embark is now compatible with any frontend tooling such as Create React App and the CLI tools for Angular, Vue, and more.
|
||||
|
||||
Previously, Embark used its own pipeline, which was compatible with most frontend frameworks by way of Webpack configuration. However, it wasn’t compatible with most frontend tooling. Embark 4 is now fully frontend-agnostic, but the old pipeline is still available if you wish to use it.
|
||||
|
||||
## Additional Updates and Features
|
||||
We’ve introduced a number of updates and new features to go along with the key features mentioned above. These include:
|
||||
|
||||
- **New contract deployment hooks**: onDeploy and afterDeploy allow for complete customization of the deployment lifecycle.
|
||||
- **Better account configuration**: accounts are now consistently defined in config/blockchain.js.
|
||||
- **Embark can be installed as a local dependency for per-project versioning**: global installation of Embark is no longer required.
|
||||
|
||||
## A new Website and Fresh New Look
|
||||
|
||||
![Website Release](/assets/images/website_release.png "Website Release")
|
||||
|
||||
Embarking into decentralized applications is exciting and fun. That’s precisely why we updated our website: to better accompany developers on their journey. Not only did we give Embark a facelift with slick new illustrations and a fresh logo, but we also made it easier to navigate developer resources such as docs, plugins, and tutorials. For developers new to Embark, the Quick Start guide will get you up and running in no time!
|
||||
|
||||
## Get Started Now
|
||||
Embark 4.0 is a great companion for those embarking into the ether! From brand new developers still learning the concepts, to seasoned pros with a specific project in mind, Embark is the ideal all-in-one development platform for building and deploying decentralized applications. Whether developing DApps end-to-end or simply deploying smart contracts, Embark allows developers to pick and choose which features, plugins, and tools to integrate.
|
||||
|
||||
Check out the [Quick Start guide](/docs/quick_start.html) or dive right into the [documentation](/docs).
|
||||
|
||||
Chat with us in [Gitter](https://gitter.im/embark-framework/Lobby)
|
||||
Star the repo on [GitHub](https://github.com/embark-framework/embark)
|
||||
Follow us on [Twitter](https://twitter.com/EmbarkProject)
|
After Width: | Height: | Size: 2.8 MiB |
After Width: | Height: | Size: 272 KiB |
After Width: | Height: | Size: 452 KiB |
After Width: | Height: | Size: 3.3 MiB |
After Width: | Height: | Size: 448 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 2.1 MiB |
After Width: | Height: | Size: 220 KiB |
After Width: | Height: | Size: 2.5 MiB |
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 516 KiB |
After Width: | Height: | Size: 822 KiB |
After Width: | Height: | Size: 627 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 2.7 MiB |
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 292 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 942 KiB |
After Width: | Height: | Size: 308 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 9.2 MiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 381 KiB |
After Width: | Height: | Size: 288 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 1008 KiB |
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="/icon/mstile-70x70.png"/>
|
||||
<square150x150logo src="/icon/mstile-150x150.png"/>
|
||||
<square310x310logo src="/icon/mstile-310x310.png"/>
|
||||
<wide310x150logo src="/icon/mstile-310x150.png"/>
|
||||
<TileColor>#2f83cd</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
|
@ -0,0 +1,9 @@
|
|||
title: Chat
|
||||
layout: chat
|
||||
---
|
||||
|
||||
<p>
|
||||
Join us at <a href="https://gitter.im/embark-framework/Lobby">gitter.im/embark-framework/Lobby</a>
|
||||
</p>
|
||||
<iframe src="https://gitter.im/embark-framework/Lobby/~embed" style="width: 100%; height: 800px; border: 1px solid black"></iframe>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
title: Join the growing community
|
||||
tagline: Get support, give support and help building Embark.
|
||||
link:
|
||||
text: Join the conversation
|
||||
href: https://gitter.im/embark-framework/Lobby
|
||||
layout: community
|
||||
---
|
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 157 KiB |
|
@ -0,0 +1,7 @@
|
|||
title: Bamboo Documentation
|
||||
---
|
||||
|
||||
The documentation for Bamboo can be found [here](https://github.com/pirapira/bamboo)
|
||||
|
||||
To use Bamboo with Embark you will need to first install the [embark-bamboo](https://github.com/embark-framework/embark-bamboo) plugin
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
title: Accounts Blockchain configuration
|
||||
layout: docs
|
||||
---
|
||||
|
||||
In our [guide on configuring a blockchain client](/docs/blockchain_configuration.html), we've configured accounts in several places.
|
||||
|
||||
This parameter let's us customize what the `accounts` will be for our deployment, as well as the accounts that will be created and unlocked by our blockchain client.
|
||||
|
||||
## Accounts parameters
|
||||
|
||||
```
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
numAddresses: "1",
|
||||
password: "config/development/devpassword"
|
||||
},
|
||||
{
|
||||
privateKey: process.env.MyPrivateKey
|
||||
},
|
||||
{
|
||||
privateKeyFile: "path/to/file",
|
||||
password: process.env.MyKeyStorePassword
|
||||
},
|
||||
{
|
||||
mnemonic: process.env.My12WordsMnemonic,
|
||||
addressIndex: "0",
|
||||
numAddresses: "1",
|
||||
hdpath: "m/44'/60'/0'/0/"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The `accounts` configuration is an array of objects, where each object represents one or more account.
|
||||
|
||||
Embark offers you multiple ways to include your account. You can use the one you prefer and ignore the others or mix and match.
|
||||
|
||||
{% notification danger 'Using production keys' %}
|
||||
Be careful when using production keys and mnemonics (ie the account where you have real money).
|
||||
|
||||
We recommend using [environment variables](https://www.schrodinger.com/kb/1842) for plain text values like `privateKey` and `mnemonic` and to put the files for `privateKeyFile` and key stores either out of your working directory or ignored by versioning (eg: add the file in gitignore)
|
||||
{% endnotification %}
|
||||
|
||||
### Parameter descriptions
|
||||
|
||||
- **nodeAccounts**: The actual node accounts, that get created and unlocked by the client
|
||||
- Can be enabled by setting it to `true`
|
||||
- **numAddresses**: Number of addresses you want from the node. Defaults to `1`
|
||||
- **password**: Password file where the password to create and unlock the accounts is. This is required
|
||||
- **privateKey**: Private key string
|
||||
- **privateKeyFile**: Either a file containing comma separated private keys or a keystore (password needed for the latter)
|
||||
- **password**: Password string for the keystore
|
||||
- **mnemonic**: 12 word mnemonic
|
||||
- **addressIndex**: Index where to start getting the addresses. Defaults to `0`
|
||||
- **numAddresses**: Number of addresses to get. Defaults to `1`
|
||||
- **hdpath**: HD derivation path. Defaults to `"m/44'/60'/0'/0/"`
|
||||
|
||||
{% notification info 'Old parameters' %}
|
||||
Please note, `account` has been replaced by `nodeAccounts` and `simulatorMnemonic` by adding a `mnemonic` account in the `accounts` array
|
||||
{% endnotification %}
|
|
@ -0,0 +1,178 @@
|
|||
title: Blockchain client configuration
|
||||
layout: docs
|
||||
---
|
||||
|
||||
When in developing, to interact with a blockchain, it is necessary to use a local Ethereum node, either using a simulator or a client like Geth or Parity. In this guide we'll explore how to configure a blockchain client we want Embark to connect to. Embark uses the `blockchain.js` file inside the `./config` folder by default for blockchain related configurations. This [can be configured](/docs/configuration.html#config) to different locations if we want to.
|
||||
|
||||
Embark offers a lot of configuration options and most of them already come with a decent default so we can start right away.
|
||||
|
||||
## Common Parameters
|
||||
|
||||
Here are the common parameters. Again, most of them come with a decent default so don't get too overwhelmed by the amount of options available:
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
default: {
|
||||
enabled: true,
|
||||
rpcHost: "localhost",
|
||||
rpcPort: 8545,
|
||||
rpcCorsDomain: {
|
||||
auto: true,
|
||||
additionalCors: ['localhost:9999']
|
||||
},
|
||||
wsRPC: true,
|
||||
wsOrigins: {
|
||||
auto: true,
|
||||
additionalCors: []
|
||||
},
|
||||
wsHost: "localhost",
|
||||
wsPort: 8546
|
||||
},
|
||||
development: {
|
||||
ethereumClientName: "geth",
|
||||
ethereumClientBin: "geth",
|
||||
datadir: ".embark/development/datadir",
|
||||
networkType: "custom",
|
||||
networkId: 1337,
|
||||
isDev: true,
|
||||
nodiscover: true,
|
||||
maxpeers: 0,
|
||||
proxy: true,
|
||||
targetGasLimit: 8000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Similar to [configuring Smart Contracts](/docs/contracts_configuration.html), this config contains environments that help configuring certain parameters differently depending of the environment. You can read more about [environments here](https://embark.status.im/docs/environments.html).
|
||||
|
||||
### Parameter descriptions
|
||||
|
||||
Most of the options are self-explanatory, still, here are some brief descriptions.
|
||||
|
||||
Option | Type: `default` | Value
|
||||
--- | --- | ---
|
||||
`enabled` | boolean: `true` | Whether or not to spawn an Ethereum node
|
||||
`rpcHost` | string: `localhost` | Host the RPC server listens to
|
||||
`rpcPort` | number: `8545` | Port the RPC server listens to
|
||||
`rpcCorsDomain` | object | The CORS domains the node accepts
|
||||
`rpcCorsDomain.auto` | | When set to true, Embark checks your other configurations to set the CORS domains. This only adds the required domains.
|
||||
`rpcCorsDomain.additionalCors` | | Manual list of CORS domains to accept. If `auto` is set to `true`, any URLs specified here will be applied *in addition to* those automatically added with `auto`.
|
||||
`wsRPC` | boolean: `true` | Whether or not to enable the Websocket server
|
||||
`wsOrigins` | object | Same as `rpcCorsDomain`, but for the Websocket server
|
||||
`wsHost` | string: `localhost` | Same as `rpcHost`, but for the Websocket server
|
||||
`wsPort` | number: `8546` | Same as `rpcPort`, but for the Websocket server
|
||||
`ethereumClientName` | string: `geth` | Client to use for the Ethereum node. Currently supported: `geth` and `parity`
|
||||
`ethereumClientBin` | string: `geth` | Path to the client binary. By default, Embark uses the client name as an executable (if it is in the PATH)
|
||||
`datadir` | string | Directory where to put the Node's data (eg: keystores)
|
||||
`networkType` | string: `custom` | Can be: `testnet`, `rinkeby`, `kovan` or custom, in which case, it will use the specified `networkId`
|
||||
`networkId` | number: `1337` | Used when `networkType` is set as `custom`. [List of known network ids](https://github.com/ethereumbook/ethereumbook/blob/3e8cf74eb935d4be495f4306b73de027af95fd97/contrib/devp2p-protocol.asciidoc#known-current-network-ids)
|
||||
`isDev` | boolean: `true` | Whether or not to use the development mode of the Node. This is a special mode where the node uses a development account as defaultAccount. This account is already funded and transactions are faster. It is recommended to start by using `isDev: true` for you project, as it is faster and safer
|
||||
`nodiscover`| boolean: `true` | Disables the peer discovery mechanism when set to `true`
|
||||
`maxpeers` | number: `0` | Maximum number of network peers
|
||||
`proxy` | boolean: `true` | Whether or not Embark should use a proxy to add functionalities. This proxy is used by Embark to see the different transactions that go through, for example, and shows them to you.
|
||||
`targetGasLimit` | number | Artificial target gas floor for the blocks to mine
|
||||
|
||||
{% notification info 'Using Parity and Metamask' %}
|
||||
|
||||
Parity has very strict CORS policies. In order to use it with Metamask (or any other browser extension), you need to add the extension's URL in the CORS.
|
||||
|
||||
You can do so by opening Metamask in its own tab. Then, copy the URL. It will look something like `chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn`.
|
||||
|
||||
Afterwards, in your blockchain config, add it to `additionalCors` of `rpcCorsDomain` and `wsOrigins`.
|
||||
{% endnotification %}
|
||||
|
||||
## Privatenet configuration
|
||||
|
||||
A private network is really similar to using the development mode of a client. The biggest differences is that it does not come with a default pre-funded account and it will not use POA (proof of authority), meaning that blocks will need to be mined.
|
||||
|
||||
Luckily, Embark has settings to limit the mining to a minimum so that everything can run smoothly while testing in a more realistic environment before going to a test network.
|
||||
|
||||
### Privatenet parameters
|
||||
|
||||
Here are common parameters for private net configurations:
|
||||
```
|
||||
privatenet: {
|
||||
networkType: "custom",
|
||||
networkId: 1337,
|
||||
isDev: false,
|
||||
datadir: ".embark/privatenet/datadir",
|
||||
mineWhenNeeded: true,
|
||||
genesisBlock: "config/privatenet/genesis.json",
|
||||
nodiscover: true,
|
||||
maxpeers: 0,
|
||||
proxy: true,
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
password: "config/privatenet/password"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note that we can always use the parameters we saw in the [Common parameters section](#Common-Parameters) to override the `default` parameters.
|
||||
|
||||
### Parameter descriptions
|
||||
|
||||
Option | Type: `default` | Value
|
||||
--- | --- | ---
|
||||
`isDev` | boolean: `false` | You need to set `isDev` to false to use enable any network that isn't the development one
|
||||
`datadir` | string | Behaves the same way as stated above, but it is recommended to use a different for different networks
|
||||
`mineWhenNeeded` | boolean: `true` | Whether to always mine (`false`) or only mine when there is a transaction (`true`). It is recommended to set `mineWhenNeeded` to `true` as otherwise, you CPU will get massively used.
|
||||
`genesisBlock` | string | The genesis file to use to create the network. This file is used when creating the network. It tell the client the parameters to initiate the node with. You can read more on [genesis blocks here](https://arvanaghi.com/blog/explaining-the-genesis-block-in-ethereum/)
|
||||
`accounts` | array | Array of accounts to connect to. Go to the [Accounts configuration](/docs/blockchain_accounts_configuration.html) page to learn more on accounts
|
||||
|
||||
|
||||
## Testnet configuration
|
||||
|
||||
Test networks are networks that are public. Knowing that, if we want to connect to a node that we control, we will first need to synchronize it. This can take hours, as we need to download the blocks that we are missing from the other peers.
|
||||
|
||||
The big advantage of using a local synced node is that we have control over it and it preserves our privacy, as we aren't using a third party node. However, as mentioned, it takes a lot of time to synchronize a node and also requires a lot of computer resources, so keep it in mind if you want to go down that route.
|
||||
|
||||
### Testnet parameters
|
||||
|
||||
```
|
||||
testnet: {
|
||||
networkType: "testnet",
|
||||
syncMode: "light",
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
password: "config/testnet/password"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Here are the necessary parameters. Again, we can add more to override as you see fit.
|
||||
|
||||
### Parameter descriptions
|
||||
|
||||
Option | Type: `default` | Value
|
||||
--- | --- | ---
|
||||
`networkType` | string: `testnet` | Again, used to specify the network. `testnet` here represents Ropsten. You can change the network by using a `networkId` by changing `networkType` to `custom`
|
||||
`syncMode` | string | Blockhain sync mode
|
||||
`syncMode = 'light' `| | Light clients synchronize a bare minimum of data and fetch necessary data on-demand from the network. Much lower in storage, potentially higher in bandwidth
|
||||
`syncMode = 'fast'` | | Faster, but higher store
|
||||
`syncMode = 'full'`| | Normal sync
|
||||
`accounts` | array | Array of accounts to connect to. Go to the [Accounts configuration](/docs/blockchain_accounts_configuration.html) page to learn more on accounts
|
||||
|
||||
|
||||
## Mainnet configuration
|
||||
|
||||
Finally, the main network, a.k.a. mainnet. It may come as no surprise, but to sync to the mainnet, the step and configurations are actually the same as for a [test network](#Testnet-configuration). The only major difference is that the `networkType` needs to be `custom` with the `networkId` set to `1`.
|
||||
|
||||
```
|
||||
mainnet: {
|
||||
networkType: "custom",
|
||||
networkId: 1,
|
||||
syncMode: "light",
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true,
|
||||
password: "config/mainnet/password"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
title: Cockpit Dashboard
|
||||
layout: docs
|
||||
---
|
||||
|
||||
The first view we're getting when entering Cockpit is its Dashboard view. Consider this your central station when developing, deploying and debugging your application. It has a lot of things to offer, so let's get right to it and take a look!
|
||||
|
||||
## Overview
|
||||
|
||||
![Cockpit Dashboard](/assets/images/cockpit_dashboard.png)
|
||||
|
||||
The dashboard is separated into different sections, which each of them having a certain responsibility. Here's a brief overview of what these are.
|
||||
|
||||
- **Navigation & Search** - At the very top we have the navigation and search. Those are self explanatory, but we'll talk a bit more about search features in a few moments.
|
||||
- **Available Services** - Right below we see boxes for services monitoring. These boxes tell us what services are up and running and will respond accordingly when turned off.
|
||||
- **Interactive Console** - This is the Cockpit equivalent of Embark's [Interactive Console](/docs/using_the_console.html).
|
||||
- **Deployed Smart Contracts** - The current status of deployed Smart Contracts. Again, very similar to Embark's [Dashboard](/docs/dashboard.html).
|
||||
|
||||
## Navigation & Search
|
||||
|
||||
We can navigate through different sections and parts of Cockpit by using the links in the navigation bar as shown below. Notice that the active view is also visually reflected in the navigation as well.
|
||||
|
||||
![Cockpit Navigation](/assets/images/cockpit_navigation.gif)
|
||||
|
||||
Cockpit's search is very powerful and lets us easily search for transactions, Smart Contracts, blocks etc. from a single search bar. All we have to do is entering a valid hash, or, in case we're searching for a deployed Smart Contract, entering the name of the Smart Contract works too:
|
||||
|
||||
![Cockpit Search](/assets/images/cockpit_search.gif)
|
||||
|
||||
## Available Services
|
||||
|
||||
As mentioned above, the available services tell us which processes and services are running and available within our current Embark session. This is simply for monitoring purposes but can be very useful in case any of the processes is crashing or will be turned off.
|
||||
|
||||
The components will reflect their active state in the UI. We can test this easily disabling any of the services using their dedicated configuration files.
|
||||
|
||||
## Interactive Console
|
||||
|
||||
This is an equivalent to Embark's [Interactive Console](/docs/using_the_console.html) just in a more rich and interactive fashion. We can use the same commands, however in addition we also get something called **Smart Suggestions**.
|
||||
|
||||
When typing a command, Embark will provide decent suggestions, making it very easy to explore what command options are available!
|
||||
|
||||
![Cockpit Smart Suggestions](/assets/images/cockpit_suggestions.gif)
|
||||
|
||||
## Deployed Smart Contracts
|
||||
|
||||
As the name suggests, this sections lists all of our application's deployed Smart Contracts. Not only that, we can click on any of them and get a nice detail view of the Smart Contract within Cockpit's [Explorer](/docs/cockpit_explorer.html).
|
||||
|
||||
![Cockpit Dashboard Contracts](/assets/images/cockpit_dashboard_contracts.gif)
|
||||
|
||||
## Changing Dark and Light theme
|
||||
|
||||
Another thing that web-based user interfaces enable easily as opposed to others, is changing the appearance of an application. Cockpit implements two different themes to choose from - light and dark. The light theme is the default.
|
||||
|
||||
Toggling between light and dark theme is as easy as clicking on the cog wheel in the navigation bar and selecting the theme respectively:
|
||||
|
||||
![Cockpit change theme](/assets/images/cockpit_change_theme.gif)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
title: Using the Cockpit Debugger
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Cockpit comes with a very powerful integrated debugger similar to the one provided by the [Remix](https://remix.ethereum.org/) browser IDE. Having an integrated debugger experience right at our finger tips makes us more productive when building decentralized applications and improves our developer experience.
|
||||
|
||||
In this guide we'll take a look at how to use the debugger.
|
||||
|
||||
## Entering the Debugger
|
||||
|
||||
First things first, we need to know where to find the debugger and how to enter it. In Cockpit, basically any transaction that is a Smart Contract call can be debugged.
|
||||
|
||||
Therefore, there are different entry points to the debugger. Most of the UI components that represent transaction come with a "Debug" button. Clicking that button takes us to Cockpit's [Code Editor](/docs/cockpit_editor.html) in its debugging mode.
|
||||
|
||||
Probably the most straight forward way to do that is entering the [Explorer](/docs/cockpit_explorer.html) and simply select any transaction that is a Smart Contract call.
|
||||
|
||||
![Cockpit Enter Debugger](/assets/images/cockpit_enter_debugger.gif)
|
||||
|
||||
## Debugger View
|
||||
|
||||
Once we enter the debugger mode inside the Code Editor we'll see that Cockpit provides us with several controls to and variable scopes to debug transactions. The controls are very similar to controls in other debugger tools (such as the browser devtools).
|
||||
|
||||
![Cockpit Debugger Controls](/assets/images/cockpit_debugger_controls.png)
|
||||
|
||||
From left to right, the controls are:
|
||||
|
||||
- **Jump to previous breakpoint** - Jumps to the previous available breakpoint within the current function
|
||||
- **Jump to next breakpoint** - Jumps to the next available breakpoint within the current function
|
||||
- **Step over previous function call** - Executes the previous function without stepping into it
|
||||
- **Step over next function call** - Executes the next function without stepping into it
|
||||
- **Step into previous function call** - Steps into the previous function call and sets temporary breakpoint
|
||||
- **Step into next function call** - Steps into the next function call and sets temporary breakpoint
|
||||
|
||||
When using any of these controls, the breakpoint indicator in the source of the Smart Contract, as well as the variables in the Scopes view will update as well.
|
||||
|
||||
![Cockpit Using Debugger](/assets/images/cockpit_using_debugger.gif)
|
||||
|
||||
## Debugger variables
|
||||
|
||||
As we're using the debugger and step through function calls, Cockpit updates and outputs relevant variables within the scope of the function we're debugging. This is a very useful feature as we can see in real-time which variables contain which value.
|
||||
|
||||
The debugger provides variables in the following scopes:
|
||||
|
||||
- **contract** - These are variable members of the Smart Contract instance itself.
|
||||
- **globals** - These are global variables available in the current scope of the function that is being debugged. This includes useful values such as the current `block` as well as `msg`.
|
||||
- **locals** - These are parameter variables scoped to the function that is being debugged.
|
|
@ -0,0 +1,25 @@
|
|||
title: Deploying with Cockpit
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Cockpit offers an additional deployment experience that enables developers to deploy their application's Smart Contracts in an **iterative** and **selective** fashion. In this guide we'll take a closer look at how this works.
|
||||
|
||||
## Viewing Smart Contracts
|
||||
|
||||
When entering the deployment view, the first thing we see is the status of all our application's deployed contracts, similar to [Embark's dashboard](/docs/dashboard.html).
|
||||
|
||||
Next to their addresses, we see the arguments that have been used to initialized each and every Smart Contract.
|
||||
|
||||
Clicking on any of the Smart Contracts opens up a detail view through which we even can call methods associated to that Smart Contract.
|
||||
|
||||
![Cockpit Contract View](/assets/images/cockpit_contracts_view.gif)
|
||||
|
||||
## Selective deployment
|
||||
|
||||
With the deployment view, it's possible to selectively deploy any of our application's Smart Contracts. By default, Embark will take care of deploying our Smart Contracts based on configurations discussed in our guide on [managing accounts](/docs/blockchain_accounts_configuration.html).
|
||||
|
||||
However, we can use an injected Web3 instance, for example provided by extensions like Metamask, and select individual Smart Contracts for deployment.
|
||||
|
||||
To make this work, we need to have Metamask setup, and activate the "Injected Web3" switch as shown below. Once activated, we can specify values used for initializing our Smart Contracts, as well as performing a gas estimation:
|
||||
|
||||
![Cockpit Selective Deployment](/assets/images/cockpit_selective_deployment.gif)
|
|
@ -0,0 +1,15 @@
|
|||
title: Cockpit Editor
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Cockpit comes with a web-based code editor that enables us to change our application's source code on the fly. Just like any other typical editor, it has a file tree, can open multiple source files and lets us add and delete files.
|
||||
|
||||
## Additional features for Smart Contract files
|
||||
|
||||
When opening a Smart Contract source file in Cockpit's editor, we'll get additional features and options to work with. These features are **Interact**, **Details** and **Transactions**, very similar to the ones we've discussed in [Exploring Smart Contracts](/docs/cockpit_explorer.html#Exploring-Smart-Contracts).
|
||||
|
||||
Cockpit will open up dedicated view for each of these in another in-pane window as shown below:
|
||||
|
||||
|
||||
![Cockpit Navigation](/assets/images/cockpit_editor.gif)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
title: Cockpit Explorer
|
||||
layout: docs
|
||||
---
|
||||
|
||||
The Explorer view in Cockpit provides an easy and accessible way to browse your way through Smart Contracts, accounts, blocks and transactions.
|
||||
|
||||
## Overview
|
||||
|
||||
In the overview of the Explorer we see a aggregation of available accounts, mind blocks and performed transactions. Each of these are paginated, making it easy to navigate through them:
|
||||
|
||||
![Cockpit Explorer Overview](/assets/images/cockpit_explorer_overview.png)
|
||||
|
||||
## Viewing Accounts
|
||||
|
||||
Account detail view can be accessed by clicking on an account address or hash. The account detail view then not only displays data about the account, but also all transactions associated with the account.
|
||||
|
||||
This makes it very convenient to find transactions, if we're aiming to find any that belong to a certain account.
|
||||
![Cockpit Contract View](/assets/images/cockpit_explorer_account.gif)
|
||||
|
||||
## Viewing Blocks
|
||||
|
||||
Blocks can be inspected as well, just by clicking on their hash. This will open up a block detail view that contains all the data attached to the block in question, including transactions that ended up in there.
|
||||
|
||||
![Cockpit Explorer Block](/assets/images/cockpit_explorer_block.png)
|
||||
|
||||
## Viewing Transactions
|
||||
|
||||
It's no surprise that Cockpit's Explorer offers a view for inspecting transactions as well. Clicking on a transaction hash takes us to the detail view, rendering all relevant transaction data.
|
||||
|
||||
![Cockpit Contract View](/assets/images/cockpit_explorer_transactions.gif)
|
||||
|
||||
## Exploring Smart Contracts
|
||||
|
||||
We can extract a lot of information about our deploy Smart Contract instances in the Explorer. When entering a Smart Contract view, we can choose to open any of the available tabs:
|
||||
|
||||
- **Interact** - Call methods on your Smart Contract instance
|
||||
- **Details** - View the ABI and byte code of your Smart Contract
|
||||
- **Transactions** - List and explore all transactions and events associated with that Smart Contract instnace
|
||||
|
||||
|
||||
![Cockpit Contract View](/assets/images/cockpit_explorer_contracts_detail.gif)
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
title: Introduction to Cockpit
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Probably one of Embark's most exciting features is its companion web interface Cockpit. With Cockpit, we're aiming for offering an alternative and more powerful interface to make developing and debugging decentralized applications even easier.
|
||||
|
||||
Throughout the following sections we'll take a closer look at Cockpit's features and how we can use it to improve our developer experience when working with Embark!
|
||||
|
||||
{% notification info 'A note on Cockpit availability' %}
|
||||
Please note that Cockpit is a feature that ships since Embark version 4.0 and is not available in or for previous versions of Embark.
|
||||
{% endnotification %}
|
||||
|
||||
## What is Cockpit?
|
||||
|
||||
Cockpit is a web application that ships with Embark since version 4.0. It's an alternative and sometimes more powerful interface in comparison to [Embark's dashboard](/docs/dashboard.html), that connects to any existing Embark process. Since it's web-based, Cockpit offers a much richer and more interactive user interface, with additional tools and features that can't be found in Embark otherwise.
|
||||
|
||||
Some of Cockpit's features are:
|
||||
|
||||
- **Dashboard** - A highly interactive real-time dashboard with service monitoring and deployment status
|
||||
- **Selective Deployment** - A deployment interface that gives you fine-grain control over when and how your Smart Contracts are deployed
|
||||
- **Explorer** - A built-in blockchain explorer making it easy to explore blocks, transactions and accounts
|
||||
- **Code Editor** - A web-based code editor, enabling changing your application's source on the fly
|
||||
- **Utilities** - Powerful utility tools, including ENS, a transaction decoder and more
|
||||
|
||||
Cockpit is actively developed and implements new features on a regular basis.
|
||||
|
||||
## Starting Cockpit
|
||||
|
||||
Cockpit can be used as soon as Embark has been spinned up in any of our applications. As mentioned in our guide on [running apps](/docs/running_apps.html), Embark will start Cockpit as part of the run process. In fact, Embark even outputs a message, telling us how to open Cockpit:
|
||||
|
||||
```
|
||||
Access the web backend with the following url: http://localhost:55555?token=xxxxx-xxxxx-xxxxx-xxxxx
|
||||
```
|
||||
|
||||
Notice that `token` is a security measurement so nobody else can access your Embark processes through Cockpit (unless they have the token). To make it a little more secure, tokens are one-time use only. If we're trying to access the same session through different browser instances, one instance won't be able to connect.
|
||||
|
||||
We can always generate a new token inside [Embark's interactive console](/docs/using_the_console.html) using the [`token` command](/docs/using_the_console.html#Retrieving-authentication-tokens-for-Cockpit).
|
||||
|
||||
## Entering the Cockpit
|
||||
|
||||
Once Embark is running, entering the Cockpit is really just a matter of opening the URL shown by Embark. Let's explore what Cockpit has to offer in the following guides.
|
||||
|
||||
Bon Voyage!
|
|
@ -0,0 +1,4 @@
|
|||
title: Cockpit Utilities
|
||||
layout: docs
|
||||
---
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
title: Configuring Embark
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Embark offers a lot of fine control when it comes to configuring the different parts of our decentralized application. In this guide we'll take a closer look at the basic configuration options provided by our application's `embark.json` file, which are mainly relevant for, but not restricted to, our application structure.
|
||||
|
||||
For configuration options related to connecting to a blockchain client, [deploying your Smart Contracts](contracts_configuration.html), [decentralized storage](storage_configuration.html), [Whisper](messages_configuration) or [ENS](naming_configuration.html), please head over to the dedicated configuration guides respectively.
|
||||
|
||||
## Overview
|
||||
|
||||
Every application [created with Embark](create_project.html) comes with an `embark.json` file. This file configures where Embark has to look for Smart Contract files and assets, as well as plugins options. Here's what a freshly scaffolded `embark.json` file could look like:
|
||||
|
||||
```
|
||||
{
|
||||
"contracts": ["contracts/**"],
|
||||
"app": {
|
||||
"js/dapp.js": ["app/dapp.js"],
|
||||
"index.html": "app/index.html",
|
||||
"images/": ["app/images/**"]
|
||||
},
|
||||
"buildDir": "dist/",
|
||||
"generationDir": "embarkArtifacts",
|
||||
"config": "config/",
|
||||
"versions": {
|
||||
"web3": "1.0.0-beta",
|
||||
"solc": "0.4.25",
|
||||
"ipfs-api": "17.2.4"
|
||||
},
|
||||
"plugins": {
|
||||
},
|
||||
"options": {
|
||||
"solc": {
|
||||
"optimize": true,
|
||||
"optimize-runs": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's look at the different options and learn what they do and mean.
|
||||
|
||||
### contracts
|
||||
|
||||
This is a list of directories in which Embark should look for Smart Contract files. These typically are globbing patterns (e.g `["contracts/**/*.sol"]` will match all sol files inside any folders inside `contracts/`).
|
||||
|
||||
### app
|
||||
|
||||
Everything inside `app` configures the assets of our application, which could be anything from HTML, JavaScript to CSS and other assets. JavaScript source files are compiled using webpack to create a bundle, all other file types are simply copied to the specified destination.
|
||||
|
||||
In the key/value pairs of `app`, every key describes the destination, while the value describes a list of glob patterns for files to be transformed and copied.
|
||||
|
||||
- **js/dapp.js** - This is the JavaScript bundle that contains our application. Specifically, all files that are defined in this option (`app/dapp.js`).
|
||||
- **index.html** - The entry point of our application (`app/index.html`)
|
||||
- **images** - All image assets of our application that we can find in `app/images/`.
|
||||
|
||||
Change these configurations as you need.
|
||||
|
||||
### buildDir
|
||||
|
||||
The directory to which the build artifacts are being moved to. Everything inside this configured folder can be considered a production ready build (default is `dist/`).
|
||||
|
||||
{% notification info 'Important note:' %}
|
||||
When using Embark with any other complementary CLI tool for building a DApp, such as Create-React-App or Angular CLI, `buildDir` should point at the same location as the complementary tool writes its distribution files to.
|
||||
|
||||
This is important so that Embark picks up the right files when doing things like [deploying your app](/docs/storage_deployment.html) on IPFS or Swarm.
|
||||
{% endnotification %}
|
||||
|
||||
### generationDir
|
||||
|
||||
A directory in which Embark is going to generate artifacts that can be used for DApp development. This include Smart Contract ABIs as well Embark specific configuration data extracted from the project's configuration. The default name of this directory is `embarkArtifacts`. To learn more about Embark Artifacts, head over to our guide on [Using EmbarkJS](/docs/javascript_usage.html).
|
||||
|
||||
### config
|
||||
|
||||
This is the location of the configuration files. There are different options to configure those:
|
||||
|
||||
* **A string** (e.g `"config/"`) - Will assume the directory in which the configuration files are located (`blockchain.js`, `contracts.js`, etc).
|
||||
* **An object**:
|
||||
* Each property would configure the path of each configuration file
|
||||
* Configuration properties can be set to false to disable the component/service in question
|
||||
|
||||
```
|
||||
...
|
||||
"config": {
|
||||
"contracts": "contracts.js",
|
||||
"blockchain": false,
|
||||
"storage": false,
|
||||
"communication": false,
|
||||
"webserver": false
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
### versions (3rd-party libraries)
|
||||
|
||||
Here you can optionally specify the versions of the library to be used by Embark. Embark will automatically download the specific library version if necessary. It's possible to override this in other configuration files such as `contracts.json` on a per environment basis.
|
||||
|
||||
### plugins
|
||||
|
||||
This is a list of installed plugins. For more information on Plugins, head over to our [Plugins guide](/docs/installing_plugins.html).
|
||||
|
||||
### options
|
||||
|
||||
The `options` property enable us to configure options for specific components and services of Embark and our application. Currently supported are options for the `solc` compiler, as well as options related to Embark's `reset` command:
|
||||
```
|
||||
...
|
||||
"options": {
|
||||
"solc": {
|
||||
"optimize": true,
|
||||
"optimize-runs": 200
|
||||
},
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## Configuring Embark's reset command
|
||||
|
||||
As mentioned in the section above, it's possible to configure how Embark behaves when [resetting projects](/docs/running_apps.html#Resetting-apps). Most of the time, the default configuration should do the trick, however, if we need more control over what files should be removed as part of the reset, we can do that using the `reset` option.
|
||||
|
||||
It comes with two properties:
|
||||
|
||||
- **defaults** - Tells Embark whether it should reset the default files it usually resets or not
|
||||
- **files** - A list of files Embark should remove as well when resetting the project
|
||||
|
||||
With these two options we have full control over what files `reset` will remove.
|
||||
|
||||
```
|
||||
...
|
||||
"options": {
|
||||
"reset": {
|
||||
"defaults": true,
|
||||
"files": ["some/other/file"]
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
|
@ -0,0 +1,33 @@
|
|||
title: Console Commands
|
||||
layout: docs
|
||||
---
|
||||
|
||||
With ``embark run`` there is a console at the bottom which can be used to interact with contracts or with embark itself. type ``help`` to see a list of available commands, more commands will be added with each version of Embark.
|
||||
|
||||
### Interacting with contracts
|
||||
After contract deployment, you should be able to interact with the web3 object and the deployed contracts.
|
||||
|
||||
### Other Available Commands
|
||||
Some commands available include:
|
||||
|
||||
* ``version`` - see list of software & libraries and their respective versions
|
||||
* ``quit`` or ``exit`` - to immediatly exit (you can also use ctrl + c)
|
||||
* ``webserver start`` - start the dev webserver
|
||||
* ``webserver stop`` - stop the dev webserver
|
||||
* ``browser open`` - open a web browser and load your DApp from the dev webserver
|
||||
|
||||
### Custom Commands
|
||||
|
||||
It's possible to extend Embark to include custom commands. See [how to create
|
||||
a plugin](creating_plugins.html)
|
||||
|
||||
<pre><code class="javascript">module.exports = function(embark) {
|
||||
embark.registerConsoleCommand(function(cmd, options) {
|
||||
if (cmd === "hello") {
|
||||
return "hello there!";
|
||||
}
|
||||
// continue to embark or next plugin;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
</code></pre>
|
|
@ -0,0 +1,555 @@
|
|||
title: Configuring Smart Contracts
|
||||
layout: docs
|
||||
---
|
||||
|
||||
As many decentralized applications are powered by Smart Contracts, configuring and deploying them should be easy. That's why Embark offers a declarative approach to define what Smart Contracts we're interested in to deploy, what their dependencies are, as well as what they should be initialized with once instantiated. This guide will explore the different configuration options that help making deploying Smart Contracts a breeze.
|
||||
|
||||
## Basic Smart Contract Configuration
|
||||
|
||||
Unless specified differently in our application's `embark.json`, Smart Contracts are configured either in the `config/contracts.js` file, or, if we're dealing with a [Smart Contract only app](create_project.html#Creating-%E2%80%9Ccontracts-only%E2%80%9D-apps), the `./contracts.js` file in the root of our project.
|
||||
|
||||
A Smart Contract configuration is placed in an environment's `contracts` property, with the name of the Smart Contract being the identifier. The following code creates a configuration for the `SimpleStorage` contract in the `development` environment:
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
...
|
||||
development: {
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Smart Contracts can be configured differently per environment just by adding a dedicated configuration to the corresponding environment. Head over to our [guide on environments](environments.html) to learn more about this.
|
||||
|
||||
### Configuring constructor parameters
|
||||
|
||||
Often, Smart Contracts need to be initialized with certain values right after they have been deployed. We can configure a Smart Contract's constructor parameters using the `args` property. `args` is either a list of values, which will be applied to the Smart Contract's constructor parameters in the same order they are defined, or it can be an object specifying the parameters using named keys.
|
||||
|
||||
```
|
||||
...
|
||||
development: {
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100]
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
The following configuration configures the `SimpleStorage`'s `initialValue` parameter, assuming that that one exists. Notice that by using this syntax, the order of constructor parameter values doesn't matter anymore:
|
||||
|
||||
```
|
||||
...
|
||||
development: {
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: {
|
||||
initialValue: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### Configuring gas and gas price
|
||||
|
||||
Both, `gas` and `gasPrice` can be configured for each Smart Contract. If we don't want to configure that for every single contract, we can also specify `gas: auto` in the environment, like this:
|
||||
|
||||
```
|
||||
...
|
||||
development: {
|
||||
gas: 'auto',
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100],
|
||||
gas: 800000,
|
||||
gasPrice: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Another cool feature of Embark is that it supports human readable ether units, to improve the developer experience.
|
||||
|
||||
{% notification info 'Human readable ether units' %}
|
||||
|
||||
Embark supports **human readable ether units** in places where ether unit values are required. [Read here](#Human-readable-Ether-units) for more information.
|
||||
{% endnotification %}
|
||||
|
||||
## Configuring Smart Contract Dependencies
|
||||
|
||||
When building more complex applications, it's very common that a Smart Contract depends on another one. Embark makes it very easy to not only ensure dependency Smart Contracts are deployed before the Smart Contract in question deploys, but also accessing their deployed addresses.
|
||||
|
||||
All we have to do is specifying the name of the Smart Contract we're interested in, prefixed with a "$". Embark will then take care of figuring out in which order our Smart Contracts need to be deployed, as well as replacing all `$CONTRACT_NAME`'s with their corresponding addresses. Assuming `SimpleStorage` depends on `OtherContract`, this can be easily configured like this:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100, '$OtherContract']
|
||||
},
|
||||
OtherContract: {...}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## Disabling deployment
|
||||
|
||||
Sometimes we want to configure different behaviour for certain contracts within different [environments](environments.html). One of those cases could be that we don't actually want to deploy `SimpleStorage` in the production environment as we might expect some other storage Smart Contract to already be somewhere out there.
|
||||
|
||||
We can prevent Embark from deploying any of our Smart Contracts by using the `deploy` configuration and setting it to `false` like this:
|
||||
|
||||
```
|
||||
...
|
||||
development:
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100]
|
||||
}
|
||||
}
|
||||
},
|
||||
production: {
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
deploy: false
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## Deployment strategies
|
||||
|
||||
In order to give users full control over which Smart Contracts should be deployed, Embark comes with a configuration feature called "deployment strategies". Deployment strategies tell Embark whether it should deploy all of the user's Smart Contracts (and its (3rd-party) dependencies, or just deploy individual Smart Contracts.
|
||||
|
||||
There are two possible strategy options:
|
||||
|
||||
- **implicit** - This is the default. Using the `implicit` strategy, Embark tries to deploy all Smart Contracts configured in the `contracts` configuration, including its (3rd-party) dependencies.
|
||||
- **explicit** - Setting this option to `explicit` tells Embark to deploy the Smart Contracts specified in the `contracts` configuration without their dependencies. This can be combined with [disabling deployment](#Disabling-deployment) of individual Smart Contracts for fine control.
|
||||
|
||||
```
|
||||
contracts: {
|
||||
strategy: 'explicit' // 'implicit' is the default
|
||||
SimpleStorage: {
|
||||
deploy: false
|
||||
},
|
||||
AnotherStorage: {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Deploying multiple instances
|
||||
|
||||
In cases where we want to create multiple instances of the same Smart Contract but with, for example, different initialization values per instance, we can use the `instanceOf` property and refer to the original Smart Contract that should be deployed multiple times.
|
||||
|
||||
This can then be combined with [disabling the deployment](#Disabling-deployment) of the original Smart Contract like this:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
Currency: {
|
||||
deploy: false,
|
||||
},
|
||||
Usd: {
|
||||
instanceOf: 'Currency',
|
||||
args: [200]
|
||||
},
|
||||
MyCoin: {
|
||||
instanceOf: 'Currency',
|
||||
args: [300]
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
In the example above, we deploy `Usd` and `MyCoin` as instances of `Currency`. Notice that `Currency` itself isn't going to be deployed but merely functions as a "recipe" to create other instances of it.
|
||||
|
||||
## Referencing already deployed Smart Contracts
|
||||
|
||||
Embark not only integrates with the Smart Contracts that we create and own, but also with Smart Contracts that are already deployed and potentially owned by third-parties. If we wish to reference such a Smart Contract instance, all we have to do is specify the `address` of the instance in question.
|
||||
|
||||
The following example configures `UserStorage` to be a Smart Contract instance that's already deployed:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
UserStorage: {
|
||||
address: '0x123456'
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## Configuring source files
|
||||
|
||||
By default Embark will look for Smart Contracts inside the folder that's configured in the application's [embark.json](configuration.html#contracts), the default being the `contracts` folder. However, if we want to change the location to look for a single Smart Contract's source, or need to compile a third-party Smart Contract to get hold of its ABI, we can do so by using the `file` property.
|
||||
|
||||
`file` specifies a full path to a file that contains the source code for the Smart Contract in question.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
file: './some_folder/simple_storage.sol',
|
||||
args: [100]
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
If Embark doesn't find the file in the specified path, it'll expect it to be a path inside installed `node_modules` dependencies. The following example configures a source file path that points to a third-party Smart Contract that is installed as a dependency:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
ERC20: {
|
||||
file: 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol'
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Embark even supports reading the source from `https`, `git`, `ipfs` and `bzz` URIs, enabling us to compile Solidity Smart Contracts that aren't even located in our local machine.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
ERC725: {
|
||||
file: 'git://github.com/status/contracts/contracts/identity/ERC725.sol#develop'
|
||||
},
|
||||
ERC725: {
|
||||
file: 'github.com/status/contracts/contracts/identity/ERC725.sol'
|
||||
},
|
||||
Ownable: {
|
||||
file: 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol'
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## Providing ABIs
|
||||
|
||||
In order to use Smart Contract instances created by web3 either in [deployment hooks](Deployment-hooks) or in [Embark's JavaScript client](contracts_javascript.html), Embark needs to get hold of the Smart Contracts' ABIs and pass those on to web3.
|
||||
|
||||
This is not a problem when dealing with Smart Contracts that we own, or at least have [access to their sources](#Configuring-source-files) so we Embark can compile them accordingly. However, if we don't have either the source, nor do we want to create a Solidity interface ourselves for Embark to compile, we can provide an already defined ABI for a dedicated Smart Contract using the `abiDefinition` property, so Embark can make use of that.
|
||||
|
||||
The following example configures `SimpleStorage` to be already deployed somewhere, but we'd still like to use the web3 instance in our `afterDeploy` hook.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
address: '0x0bFb07f9144729EEF54A9057Af0Fcf87aC7Cbba9',
|
||||
abiDefinition: [...]
|
||||
}
|
||||
},
|
||||
afterDeploy: async (deps) => {
|
||||
const value = await deps.contracts.SimpleStorage.methods.get().call();
|
||||
console.log(value);
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
`afterDeploy` and other deployment hooks are covered in [Deployment Hooks](#Deployment-hooks).
|
||||
|
||||
## Providing Artifacts
|
||||
|
||||
Similar to providing ABIs, providing an Embark artifact lets you configure your contract using an already generated artifact.
|
||||
|
||||
That way, you don't need to have the contract on disk or even deploy it, if the address is specified in it.
|
||||
|
||||
Here is how you can do it:
|
||||
|
||||
<pre><code class="javascript">...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
artifact: './path/to/SimpleStorage.json'
|
||||
}
|
||||
}
|
||||
...
|
||||
</code></pre>
|
||||
|
||||
## Deployment tracking
|
||||
|
||||
Embark's Smart Contract deployment mechanism prevents the deployment of Smart Contracts that have already been deployed. This turns out to be a powerful feature as you don't have to worry about keeping track of it. The way this works is that, by default, Embark creates a file `./embark/chains.json` in which it stores the name and address of the deployed Smart Contracts. That information is then mapped to the hash of the block in which the Smart Contract have been deployed:
|
||||
|
||||
```
|
||||
{
|
||||
BLOCK_HASH {
|
||||
"contracts": {
|
||||
HASH(NAME, BYTECODE, ARGS, ADDRESS): {
|
||||
"name": NAME,
|
||||
"address: ADDRESS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With concrete data, the contents of `chains.json` could look something like this:
|
||||
|
||||
```
|
||||
{
|
||||
"0x6454b3e22cc9abe24bcd9f239687ad68ab6addb4f271a6b955f2e6111522310a": {
|
||||
"contracts": {
|
||||
"0x3043b04ad856d169c8f0b0509c0bc63192dc7edd92d6933c58708298a0e381be": {
|
||||
"name": "ENSRegistry",
|
||||
"address": "0x4F75E2beCbD08c5dD67f74aA0E28558a6a596528"
|
||||
},
|
||||
"0xc51636fc4431a598f31d35de56a5e59b1a55d601babbdf5e9718a520654a4a93": {
|
||||
"name": "Resolver",
|
||||
"address": "0xD9c5bEeD72A0f2FeAcF43730eF2B4bC86F38Cb6f"
|
||||
},
|
||||
"0x269ef61966bd985f10d8ae13d7eaa498b423372f266fb5c188f60fa5618ff334": {
|
||||
"name": "FIFSRegistrar",
|
||||
"address": "0xe7120Bfe50b72a9629546dCe05c3821b3bb52B4E"
|
||||
},
|
||||
"0xc920172104d0372dfa1375d4c9ef05ae15569b94b88fd4b0d5a834965dc7420b": {
|
||||
"name": "SimpleStorage",
|
||||
"address": "0x4928bFf909063465d3cc1708E5F9c6EB0E3F324E"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Disabling tracking
|
||||
|
||||
If we prefer to have full control over the deployment process and don't want Embark to keep track of individual Smart Contract deployments, we use the `track` configuration and set it `false`.
|
||||
|
||||
The following example ensures `ERC20` won't be tracked and therefore redeployed in every deployment cycle.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
ERC20: {
|
||||
track: false
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### Specifying a tracking file
|
||||
|
||||
In addition to enabling and disabling tracking, it's also possible to tell Embark which file it should use for tracking. This can be useful for tracking deployed Smart Contracts on different platforms, such as testnets and the mainnet. The tracking state of those platforms should most likely be under version control, because we certainly don't want multiple people to redeploy our Smart Contracts on multiple platforms. Putting those files under version control ensures everybody else gets the already tracked state. The contents will have the same schema as discussed above.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
ERC20: {
|
||||
track: 'path/to/some/file'
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Having the file referenced above under version control ensures that other users of our project don't redeploy the Smart Contracts on different platforms.
|
||||
|
||||
## Deployment hooks
|
||||
|
||||
Sometimes we want to execute certain logic when Smart Contracts are being deployed or after all of them have been deployed. In other cases, we'd even like to control whether a Smart Contract should be deployed in the first place. For those scenarios, Embark lets us define the deployment hooks `beforeDeploy`, `deployIf`, `onDeploy` and `afterDeploy`.
|
||||
|
||||
Deployment hooks have access to a `dependencies` object that comes with instances of all Smart Contracts that are defined as dependency of the hooks using the `deps` property of the Smart Contract in question, and the Smart Contract itself. In addition to all relevant Smart Contract instances, this object also exposes the current `web3` instance and a `logger` instance as shown in the examples below.
|
||||
|
||||
### Conditional Deployment with `deployIf`
|
||||
|
||||
We can specify a condition that decides whether a contract should be deployed by using the `deployIf` hook. `deployIf` is a function that either returns a promise or is created using `async/await` syntax and has to resolve to a boolean value. If the resolve value is `true`, the Smart Contract in question will be deployed. If it's `false`, Embark will skip deploying the Smart Contract in question.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
ERC20: {
|
||||
deployIf: async (dependencies) => {
|
||||
return await dependencies.contracts.Manager.methods.isUpdateApproved().call();
|
||||
},
|
||||
deps: ['Manager']
|
||||
},
|
||||
Manager: {...}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Notice how `dependencies.contracts` gives access to the `Manager` contract instance. This however, is only possible because `Manager` has been defined as dependency of `ERC20` using the `deps` property. If we're using a Node version that doesn't support async/await, the same can be achieved using promises like this (web3 APIs already return promises):
|
||||
|
||||
```
|
||||
...
|
||||
ERC20: {
|
||||
deployIf: (dependencies) => {
|
||||
return dependencies.contracts.Manager.methods.isUpdateApproved().call();
|
||||
},
|
||||
deps: ['Manager']
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
### `beforeDeploy` hook
|
||||
|
||||
`beforeDeploy` is a hook that, just like the name says, is executed before something is deployed. This hook is the counterparts to the [afterDeploy](#afterDeploy-hook) and can be used in either individual Smart Contract configurations, or for all Smart Contracts. E.g. the following snippet configures `beforeDeploy` just for `SimpleStorage`:
|
||||
|
||||
```
|
||||
SimpleStorage: {
|
||||
beforeDeploy: async () => {
|
||||
console.log('before deploying SimpleStorage');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Wheras this configuration here runs the hook before all Smart Contracts are being deployed:
|
||||
|
||||
```
|
||||
contracts: {
|
||||
SimpleStorage: { ... }
|
||||
beforeDeploy: async () => {
|
||||
console.log('Before all deploy');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `onDeploy` hook
|
||||
|
||||
We can specify the `onDeploy` hook to execute code, right after a contract has been deployed. Just like `deployIf` and `afterDeploy`, `onDeploy` is a function that has access to the Smart Contract's dependencies defined in its `deps` property. The following example executes `SimpleStorage`'s `set()` method, once deployed.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100],
|
||||
onDeploy: async (dependencies) => {
|
||||
await dependencies.contracts.SimpleStorage.methods.set(150).send({from: dependencies.web3.web3.eth.defaultAccount});
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
To actually `send` transactions and not just make `call`s, you will probably need to provide a `from` account. You can use the `web3` instance inside `dependencies` to get the `defaultAccount` as above.
|
||||
|
||||
Also, as mentioned above, every deployment hook works with plain promises as well:
|
||||
|
||||
```
|
||||
...
|
||||
SimpleStorage: {
|
||||
args: [100],
|
||||
onDeploy: (dependencies) => {
|
||||
return dependencies.contracts.SimpleStorage.methods.set(150).send();
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### `afterDeploy` hook
|
||||
|
||||
If we want to execute code once all of our Smart Contracts have been deployed, Embark has got us covered with the `afterDeploy` hook. The same rules apply here. `afterDeploy` has access to all deployed contract instances through the `dependencies` object.
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100]
|
||||
},
|
||||
},
|
||||
afterDeploy: (dependencies) => {
|
||||
dependencies.contracts.SimpleStorage.methods.set(150).send({from: dependencies.web3.eth.defaultAccount});
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Since we use functions for these deployment hooks, we have to manage errors ourselves. We skipped that step in the above examples to save space, but here is an easy example on how you can do it:
|
||||
|
||||
```
|
||||
onDeploy: async (dependencies) => {
|
||||
try {
|
||||
await dependencies.contracts.SimpleStorage.methods.set(85).send({from: dependencies.web3.eth.defaultAccount});
|
||||
} catch (e) {
|
||||
console.error('Error during onDeploy', e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% notification info 'A note on deployment hook string syntax' %}
|
||||
In older versions of Embark, deployment hooks have been defined as an array of strings. This is due historical reasons where configuration files used to be JSON files that don't support functions.
|
||||
|
||||
The examples above can be therefore written as:
|
||||
|
||||
<pre class="highlight">afterDeploy: ['SimpleStorage.methods.set(150).send()']
|
||||
onDeploy: ['SimpleStorage.methods.set(150).send()']
|
||||
deployIf: 'await Manager.methods.isUpdateApproved()'
|
||||
</pre>
|
||||
|
||||
This string syntax is still supported, but will be deprecated and likely be removed in future versions of Embark.
|
||||
{% endnotification %}
|
||||
|
||||
### Logging with context
|
||||
|
||||
Often we use log statements to either debug code or simply to output what's going on at the moment. It can be useful to output logs within deployment hooks as well. To make sure our deployment hooks don't drown in the rest of Embark's output, we can use the injected `logger` which prefixes every log message with a context indicator.
|
||||
|
||||
For example, when logging something from within an `onDeploy` hook of a Smart Contract, the output will look like this:
|
||||
|
||||
```
|
||||
SmartContractName > onDeploy > [YOUR MESSAGE]
|
||||
```
|
||||
|
||||
The `logger` is injected as part of the `dependencies` object, so we can use it like this:
|
||||
|
||||
```
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
onDeploy: async (dependencies) => {
|
||||
dependencies.logger.info('Hello from onDeploy!');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Which will result in
|
||||
|
||||
```
|
||||
SimpleStorage > onDeploy > Hello from onDeploy!
|
||||
```
|
||||
|
||||
## Human readable Ether units
|
||||
|
||||
Embark supports human readable ether units in different places where Ether values can be configured. A human readable ether unit is the combination of any number value and any valid ether unit, such as `wei`, `kwei`, `Kwei`, `shannon`, `finney`, ... etc.
|
||||
|
||||
Let's take the simple Smart Contract configuration from the [configuring gas and gas price](#Configuring-gas-and-gas-price) section:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100],
|
||||
gas: 800000,
|
||||
gasPrice: 5
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
This can as well be written as:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
SimpleStorage: {
|
||||
args: [100],
|
||||
gas: '800 Kwei',
|
||||
gasPrice: 5
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Embark will take care of converting those units to their dedicated Wei values.
|
|
@ -0,0 +1,225 @@
|
|||
title: Accounts & Deployment
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Embark is very flexible when it comes to configuring wallet accounts for deployment. Whether we want to deploy from generated node accounts, or our own key store file. In this guide we'll take a closer look at how to configure wallet accounts for deployment of Smart Contracts.
|
||||
|
||||
## Specifying a deployment account
|
||||
|
||||
We can specify from which account we want to deploy a Smart Contract using the `from` or `fromIndex` options of a Smart Contract's configuration. The `from` parameter is a string which can be any account address:
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
Currency: {
|
||||
from: '0xfeedaa0e295b09cd84d6ea2cce390eb443bcfdfc',
|
||||
args: [100]
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
`fromIndex` on the other hand, configures the index of the accounts array that is returned by `web3.eth.getAccounts()`. The following code configures the `Currency` Smart Contract to deployed from the first address:
|
||||
|
||||
|
||||
```
|
||||
...
|
||||
contracts: {
|
||||
Currency: {
|
||||
fromIndex: 0
|
||||
args: [100]
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
If both options are configured, `from` takes precedence.
|
||||
|
||||
## Using wallet accounts
|
||||
|
||||
We can use our own wallet accounts by specifying either a private key, a mnemonic, or a private key file. Let's take a look at how this is done.
|
||||
|
||||
{% notification danger 'A note on private keys in production' %}
|
||||
|
||||
While it's very convenient to put private keys, passphrases and mnemonics in the configuration file, we highly recommend doing this only sparingly and ideally move sensitive data into environment variables instead.
|
||||
|
||||
Please consider the following configuration options as development-only options and refer to [using environment variables for production](#Using-environment-variables-for-production), once you plan to deploy your application on Mainnet.
|
||||
|
||||
{% endnotification %}
|
||||
|
||||
### Using a private key
|
||||
|
||||
Using a private key is just a matter of adding it as `privateKey` option.
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
testnet: {
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
privateKey: "your_private_key"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This can also be set to `random` to generate a random account (useful when testing):
|
||||
|
||||
```
|
||||
privateKey: "random"
|
||||
```
|
||||
|
||||
### Using a private key store
|
||||
|
||||
Another option is to use an existing private key store file. Instead of writing the private key straight into the configuration file, we specify a path to a private key store file using the `privateKeyFile` option. Key store files need to be decrypted using a passphrase. That's why `privateKeyFile` needs to be used in combination with the `password` option, which is the passphrase needed to unlock the key store file:
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
testnet: {
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
privateKeyFile: 'path/to/key/store/file',
|
||||
password: 'some super secret password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using a Mnemonic
|
||||
|
||||
Last but not least it's also possible to use a mnemonic to generate a wallet and specify which of the wallet's addresses should be used for deployment. It's also possible to configure an HD derivation path if more control is needed.
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
testnet: {
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
mnemonic: "12 word mnemonic",
|
||||
addressIndex: "0", // Optional. The index to start getting the address
|
||||
numAddresses: "1", // Optional. The number of addresses to get
|
||||
hdpath: "m/44'/60'/0'/0/" // Optional. HD derivation path
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using node accounts
|
||||
|
||||
Some blockchain node clients such as Geth allow for generating accounts. This is very useful for development purposes. We can tell Embark to make use of those accounts, by using the `nodeAccounts` option and setting it to `true`.
|
||||
|
||||
In addition to that, we can combine the generated node accounts with our own custom accounts, simply by extending the `accounts` array with any of the configurations covered earlier:
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
testnet: {
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
nodeAccounts: true
|
||||
},
|
||||
{
|
||||
privateKey: '...'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% notification info 'Accounts order' %}
|
||||
The order in the accounts array is important. This means that using `nodeAccounts` first, as above, will set the node's account as the `defaultAccount` for deployment.
|
||||
{% endnotification %}
|
||||
|
||||
## Using environment variables for production
|
||||
|
||||
There are special security considerations to have when deploying to production. Chiefly, no private keys, private key files or mnemonics should be present in source control. Instead, we recommend using environment variables to pass those values in, like this:
|
||||
|
||||
```
|
||||
const secrets = require('secrets.json'); // should NOT be in source control
|
||||
|
||||
module.exports = {
|
||||
mainnet: {
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
privateKeyFile: secrets.privateKeyFilePath,
|
||||
password: secrets.password
|
||||
},
|
||||
{
|
||||
mnemonic: process.env.DAPP_MNEMONIC, // An environment variable is also possible
|
||||
addressIndex: "0", // Optional. The index to start getting the address
|
||||
numAddresses: "1", // Optional. The number of addresses to get
|
||||
hdpath: "m/44'/60'/0'/0/" // Optional. HD derivation path
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuring account balance for development
|
||||
When in development, we can specify the balance of each account using the `balance` option:
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
development: {
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
mnemonic: "12 word mnemonic",
|
||||
balance: "5 ether"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Balances are specified using a [human readable units](/docs/contracts_configuration.html#Human-readable-Ether-units) such as "5 ether" or "200 finney". If no unit is specified the value will be in Wei.
|
||||
|
||||
## Using accounts in arguments
|
||||
|
||||
Account can be used as arguments using Embark's built-in interpolation syntax, similar to referring to Smart Contract instances.
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
development: {
|
||||
contracts: {
|
||||
MyContractThatNeedsAccountAddresses: {
|
||||
args: ['$accounts[0]', '$accounts[4]']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Deploying to Infura
|
||||
|
||||
We can also connect to a remote Infura.io blockchain node as per instructions below. The following specifies the configuration for the web3 provider, not the blockchain node configuration itself.
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
testnet: {
|
||||
deployment:{
|
||||
accounts: [
|
||||
{
|
||||
// your accounts here, see above for details
|
||||
}
|
||||
],
|
||||
host: "rinkeby.infura.io/INFURA_TOKEN_HERE",
|
||||
port: false,
|
||||
protocol: 'https',
|
||||
type: "rpc"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
title: Special Imports
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Depending on which language for developing Smart Contracts is used, Embark supports special import paths. In this guide we'll discuss the various ways of importing Smart Contract source code in other Smart Contract files.
|
||||
|
||||
{% notification info 'Scope of this guide' %}
|
||||
The following features are currently only supported for Smart Contracts written in the Solidity programming language. If you're using another language, such as Vyper, these features may not be available.
|
||||
{% endnotification %}
|
||||
|
||||
## Importing files
|
||||
|
||||
If using Solidity it's possible to import other Smart Contract files inside a source file from the application's folders that are not explicitly defined in the `contracts` property of `embark.json`.
|
||||
|
||||
```
|
||||
import "another_folder/another_test.sol";
|
||||
```
|
||||
|
||||
## Importing from `node_modules`
|
||||
|
||||
Embark also supports convenient imports from installed `node_modules`. Just specify the package name including the path to the Solidity file you wish to import.
|
||||
|
||||
```
|
||||
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
|
||||
```
|
||||
|
||||
## Importing from remote paths
|
||||
|
||||
Source files can even be imported straight from Git, Github, IPFS, Swarm or via HTTP(S):
|
||||
|
||||
```
|
||||
import "git://github.com/status/contracts/contracts/identity/ERC725.sol#develop";
|
||||
import "github.com/status/contracts/contracts/identity/ERC725.sol";
|
||||
import "https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol";
|
||||
import "bzz:/1ffe993abc835f480f688d07ad75ad1dbdbd1ddb368a08b7ed4d3e400771dd63"
|
||||
```
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
title: Smart Contracts in JavaScript
|
||||
layout: docs
|
||||
---
|
||||
|
||||
In order to talk to our deployed Smart Contracts through a web application, we typically need a JavaScript equivalent that offers APIs to make this possible. Embark generates JavaScript artifacts for all Smart Contracts used by our application.
|
||||
|
||||
In this guide we'll take a quick look how to make use of them!
|
||||
|
||||
## Importing and using Smart Contracts
|
||||
|
||||
Embark will [automatically generate artifacts](/docs/javascript_usage.html#Embark-Artifacts) for all configured Smart Contracts in our application, making them available to an application's front-end. Allwe have to do is importing them accordingly. For example, the Smart Contract below:
|
||||
|
||||
```
|
||||
contract SimpleStorage {
|
||||
uint public storedData;
|
||||
|
||||
function SimpleStorage(uint initialValue) {
|
||||
storedData = initialValue;
|
||||
}
|
||||
|
||||
function set(uint x) {
|
||||
storedData = x;
|
||||
}
|
||||
function get() constant returns (uint retVal) {
|
||||
return storedData;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Will available as JavaScript object, after artifact generation and can be imported as:
|
||||
|
||||
```
|
||||
import { SimpleStorage } from './embarkArtifacts/contracts';
|
||||
```
|
||||
|
||||
Notice that the exact path to the Smart Contract source is configured using the `generationDir` property in [Embark's configuration](/docs/configuration.html#generationDir).
|
||||
|
||||
Once imported, Smart Contract APIs can be used as needed. The code below uses Web.js syntax and might differ from your APIs, depending on what web3 connector you've installed.
|
||||
|
||||
```
|
||||
SimpleStorage.methods.set(100).send();
|
||||
|
||||
SimpleStorage.methods.get().call().then(value => {
|
||||
console.log(value);
|
||||
});
|
||||
|
||||
SimpleStorage.methods.storedData().call().then(value => {
|
||||
console.log(value);
|
||||
});
|
||||
```
|
|
@ -0,0 +1,215 @@
|
|||
title: Testing Smart Contracts
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Testing is a crucial part of developing robust and high-quality software. That's why Embark aims to make testing our Smart Contract as easy as possible. In this guide we'll explore Embark specific testing APIs and how to write tests for our Smart Contracts.
|
||||
|
||||
## Creating tests
|
||||
|
||||
Test files resides in a project's `test` folder. Any JavaScript file within `test/` is considered a spec file and will be executed by Embark as such. A spec file contains test specs which are grouped in `contract()` functions. A single spec is written using `it()` blocks.
|
||||
|
||||
Here's what such a test could look like:
|
||||
|
||||
```
|
||||
contract('SomeContract', () => {
|
||||
it('should pass', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This is a single test spec which will always pass. We're using a globally available `assert` object to make assertions in our specs. If you're familiar with the [Mocha testing framework](https://mochajs.org), this syntax might be familiar. In fact, Embark uses Mocha as a test runner behind the scenes.
|
||||
|
||||
`contract()` is just an alias for Mocha's `describe()` function and is globally available. In general, global functions and objects are:
|
||||
|
||||
- `contract()`: Same as Mocha's `describe`
|
||||
- `config()`: Function to deploy Smart Contracts using custom configurations.
|
||||
- `web3`: Web3 object
|
||||
- `assert`: Node's assert
|
||||
- Mocha functions: `describe()`, `it()`, `before()`, etc.
|
||||
|
||||
### Importing EmbarkJS
|
||||
|
||||
If we want to use any of EmbarkJS' APIs, we can require it as expected:
|
||||
|
||||
```
|
||||
const EmbarkJS = require('Embark/EmbarkJS');
|
||||
```
|
||||
|
||||
For more information on EmbarkJS's APIs, head over to [this guide](/docs/javascript_usage.html).
|
||||
|
||||
## Running tests
|
||||
|
||||
Once we've written our tests, we can execute them using Embark's `test` command:
|
||||
|
||||
```
|
||||
$ embark test
|
||||
```
|
||||
|
||||
As mentioned earlier, this will pick up all files inside the `test/` folder and run them as test files.
|
||||
|
||||
### Running test subsets
|
||||
|
||||
If we aren't interested in running all tests but only a specific subset, we can specify a test file as part of the `test` command like this:
|
||||
|
||||
```
|
||||
$ embark test test/SomeContract_spec.js
|
||||
```
|
||||
|
||||
### Running tests against a different node
|
||||
|
||||
By default, tests are run using an Ethereum simulator ([Ganache](https://www.truffleframework.com/ganache)). We can use the `--node` option to change that behavior. Passing `--node embark` to `embark test` will use the Ethereum node associated with an already running embark process. We can also specify a custom endpoint, for example:
|
||||
|
||||
```
|
||||
$ embark test --node ws://localhost:8556
|
||||
```
|
||||
|
||||
### Outputting gas cost details
|
||||
|
||||
When running tests, we can even get an idea of what the gas costs of our Smart Contract deployments are. Embark comes with a `--gasDetails` option that makes this possible.
|
||||
|
||||
```
|
||||
$ embark test --gasDetails
|
||||
```
|
||||
|
||||
## Configuring Smart Contracts for tests
|
||||
|
||||
Very similar to how we [configure our Smart Contracts](/docs/contracts_configuration.html) for deployment on test or production nets, we have to configure them for our tests as well. This is important, so that our Smart Contracts get deployed with the correct testing data.
|
||||
|
||||
To do that, Embark adds a global `config()` function to the execution context, which uses the same API as the configuration object for our application's Smart Contracts. So if we had a `SomeContract` that should be picked up for deployment, this is what the configuration would look like:
|
||||
|
||||
```
|
||||
config({
|
||||
contracts: {
|
||||
SomeContract: {} // options as discussed in Smart Contract configuration guide
|
||||
}
|
||||
});
|
||||
|
||||
contract('SomContract', () => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
One thing that's important to note here is that, behind the scenes, Embark has to run `config()` first to deploy the Smart Contracts and only **then** starts running tests. This will have an impact on the developer experience when importing Smart Contract instances within spec files. But more on that later.
|
||||
|
||||
{% notification info 'A note on config()' %}
|
||||
The global `config()` function is used for Smart Contract deployment and therefore delays the execution of tests until deployment is done.
|
||||
{% endnotification %}
|
||||
|
||||
## Accessing Smart Contract instances
|
||||
|
||||
To write meaningful tests, we obviously want to interact with our Smart Contracts. As we know, [Embark generates Smart Contract instances](/docs/javascript_usage.html#Embark-Artifacts) for us. All we have to do is importing and using them accordingly.
|
||||
|
||||
The following code imports `SomeContract` and calls an imaginary method on it inside a spec:
|
||||
|
||||
```
|
||||
const SomeContract = require('EmbarkJS/contracts/SomeContract');
|
||||
|
||||
config({
|
||||
contracts: {
|
||||
SomeContract: {}
|
||||
}
|
||||
});
|
||||
|
||||
contract('SomeContract', () => {
|
||||
|
||||
it('should do something', async () => {
|
||||
const result = await SomeContract.methods.doSomething.call();
|
||||
assert.equal(result, 'foo');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
There's one gotcha to keep in mind though. Looking at the snippet above, it seems like we can use `SmartContract` right away once it is imported. However, this is not actually true. As mentioned earlier, Embark first has to actually deploy our Smart Contracts and until that happens, all imported Smart Contract references are empty objects.
|
||||
|
||||
This is not a problem anymore when using Smart Contract instances inside spec blocks, because we know that tests are executed after all Smart Contracts have been deployed. Embark will hydrate the imported references with actual data before the tests are run.
|
||||
|
||||
{% notification info 'Smart Contract reference hydration' %}
|
||||
Smart Contract references imported from EmbarkJS are empty until the Smart Contract are actually deployed. This means Smart Contract references can only be used inside `contract()` blocks.
|
||||
{% endnotification %}
|
||||
|
||||
## Configuring accounts
|
||||
|
||||
Accounts within the testing environment can be configured [just like we're used to](/docs/contracts_deployment.html). The same rules apply here, and [configuring an Ether balance](/docs/contracts_deployment.html#Configuring-account-balance-for-development) is supported as well. Configuring custom accounts in tests is especially useful if we want to use a specific account for our tests.
|
||||
|
||||
```
|
||||
config({
|
||||
deployment: {
|
||||
accounts: [
|
||||
{
|
||||
privateKeyFile: 'path/to/file',
|
||||
balance: '42 shannon'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Accessing Accounts
|
||||
|
||||
Obviously, we want to access all configured accounts as well. Sometimes we want to test functions or methods that require us to specify a `from` address to send transactions from. For those cases we very likely want to access any of our our available accounts.
|
||||
|
||||
All available accounts are emitted by `config()` and can be accessed using a callback parameter like this:
|
||||
|
||||
```
|
||||
let accounts = [];
|
||||
|
||||
config({
|
||||
...
|
||||
}, (err, accounts) => {
|
||||
accounts = accounts;
|
||||
});
|
||||
```
|
||||
|
||||
Notice that we're introducing a global `accounts` variable so we can mutate its state once the accounts are loaded. This is necessary to make the emitted accounts available in any of our spec blocks.
|
||||
|
||||
## Connecting to a different node
|
||||
|
||||
By default, Embark will use an internal VM to run the tests. However we can also specify a node to connect to and run the tests there, using the `host`, `port` and `type` options as shown below:
|
||||
|
||||
```
|
||||
config({
|
||||
deployment: {
|
||||
"host": "localhost",
|
||||
"port": 8545,
|
||||
"type": "rpc"
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Manually deploying Smart Contracts
|
||||
|
||||
As mentioned earlier, Embark handles the deployment of our Smart Contracts using the function `config()` function. If we wish to deploy particular Smart Contracts manually, we can do so using an imported Smart Contract reference. We just need to make sure that we're doing this inside a `contract()` block as discussed earlier:
|
||||
|
||||
```
|
||||
const SimpleStorage = require('Embark/contracts/SimpleStorage');
|
||||
|
||||
contract('SimpleStorage Deploy', () => {
|
||||
let SimpleStorageInstance;
|
||||
|
||||
before(async function() {
|
||||
SimpleStorageInstance = await SimpleStorage.deploy({ arguments: [150] }).send();
|
||||
});
|
||||
|
||||
it('should set constructor value', async () => {
|
||||
let result = await SimpleStorageInstance.methods.storedData().call();
|
||||
assert.strictEqual(parseInt(result, 10), 150);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Code coverage
|
||||
|
||||
Embark allows you to generate a coverage report for your Solidity Smart Contracts by passing the `--coverage` option on the `embark test` command.
|
||||
|
||||
```
|
||||
$ embark test --coverage
|
||||
```
|
||||
|
||||
The generated report looks something like this:
|
||||
|
||||
![Coverage Report: Files](/coverage-files.png)
|
||||
|
||||
This gives us a birds-eye view on the state of the coverage of our Smart Contracts: how many of the functions were called, how many lines were hit, even whether all the branch cases were executed. When selecting a file, a more detailed report is produced. Here's what it looks like:
|
||||
|
||||
![Coverage Report: Detailed](/coverage-report.png)
|
|
@ -0,0 +1,220 @@
|
|||
title: Contributing to Embark
|
||||
layout: docs
|
||||
---
|
||||
|
||||
We would love for you to contribute to Embark and help make it even better than it is
|
||||
today! As a contributor, here are the guidelines we would like you to follow:
|
||||
|
||||
- [Code of Conduct](#coc)
|
||||
- [Question or Problem?](#question)
|
||||
- [Issues and Bugs](#issue)
|
||||
- [Feature Requests](#feature)
|
||||
- [Submission Guidelines](#submit)
|
||||
- [Coding Rules](#rules)
|
||||
- [Commit Message Guidelines](#commit)
|
||||
|
||||
## <a name="coc"></a> Code of Conduct
|
||||
Help us keep Embark open and inclusive. Please read and follow our [Code of Conduct](https://github.com/embark-framework/embark/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
Do not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [Stack Exchange](https://ethereum.stackexchange.com/search?tab=newest&q=embark) where the questions should be tagged with tag `embark`.
|
||||
|
||||
Stack Exchange is a much better place to ask questions since:
|
||||
|
||||
- there are thousands of people willing to help
|
||||
- questions and answers stay available for public viewing so your question / answer might help someone else
|
||||
- Stack Exchange's voting system assures that the best answers are prominently visible.
|
||||
|
||||
To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Exchange.
|
||||
|
||||
If you would like to chat about the question in real-time, you can reach out via [our gitter channel](https://gitter.im/embark-framework/Lobby).
|
||||
|
||||
## <a name="issue"></a> Found a Bug?
|
||||
If you find a bug in the source code, you can help us by
|
||||
[submitting an issue](#submit-issue) to our [GitHub Repository](https://github.com/embark-framework/embark/). Even better, you can
|
||||
[submit a Pull Request](#submit-pr) with a fix.
|
||||
|
||||
## <a name="feature"></a> Missing a Feature?
|
||||
You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub
|
||||
Repository. If you would like to *implement* a new feature, please submit an issue with
|
||||
a proposal for your work first, to be sure that we can use it.
|
||||
Please consider what kind of change it is:
|
||||
|
||||
* For a **Major Feature**, first open an issue and outline your proposal so that it can be
|
||||
discussed. This will also allow us to better coordinate our efforts, prevent duplication of work,
|
||||
and help you to craft the change so that it is successfully accepted into the project.
|
||||
* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
|
||||
|
||||
## <a name="submit"></a> Submission Guidelines
|
||||
|
||||
### <a name="submit-issue"></a> Submitting an Issue
|
||||
|
||||
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
||||
|
||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide steps to reproduce your issue.
|
||||
|
||||
You can file new issues by filling out our [new issue form](https://github.com/embark-framework/embark/issues/new/choose).
|
||||
|
||||
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
||||
Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
||||
1. Search [GitHub](https://github.com/embark-framework/embark/pulls) for an open or closed PR
|
||||
that relates to your submission. You don't want to duplicate effort.
|
||||
1. Fork the embark-framework/embark repo.
|
||||
1. Make your changes in a new git branch:
|
||||
|
||||
```shell
|
||||
git checkout -b my-fix-branch master
|
||||
```
|
||||
|
||||
1. Create your patch, **including appropriate test cases**.
|
||||
1. Run the QA suite, by running `$ npm run qa` and ensure that all steps succeed.
|
||||
1. Commit your changes using a descriptive commit message that follows our
|
||||
[commit message conventions](#commit). Adherence to these conventions
|
||||
is necessary because release notes are automatically generated from these messages.
|
||||
|
||||
```shell
|
||||
git commit -a
|
||||
```
|
||||
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
|
||||
|
||||
1. Push your branch to GitHub:
|
||||
|
||||
```shell
|
||||
git push origin my-fix-branch
|
||||
```
|
||||
|
||||
1. In GitHub, send a pull request to `embark:master`.
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the test suites to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push -f
|
||||
```
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
#### After your pull request is merged
|
||||
|
||||
After your pull request is merged, you can safely delete your branch and pull the changes
|
||||
from the main (upstream) repository:
|
||||
|
||||
* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
|
||||
|
||||
```shell
|
||||
git push origin --delete my-fix-branch
|
||||
```
|
||||
|
||||
* Check out the master branch:
|
||||
|
||||
```shell
|
||||
git checkout master -f
|
||||
```
|
||||
|
||||
* Delete the local branch:
|
||||
|
||||
```shell
|
||||
git branch -D my-fix-branch
|
||||
```
|
||||
|
||||
* Update your master with the latest upstream version:
|
||||
|
||||
```shell
|
||||
git pull --ff upstream master
|
||||
```
|
||||
|
||||
## <a name="rules"></a> Coding Rules
|
||||
To ensure consistency throughout the source code, keep these rules in mind as you are working:
|
||||
|
||||
* All public API methods **must be documented**.
|
||||
|
||||
## <a name="commit"></a> Commit Message Guidelines
|
||||
|
||||
We have very precise rules over how our git commit messages can be formatted. This leads to **more
|
||||
readable messages** that are easy to follow when looking through the **project history**. But also,
|
||||
we use the git commit messages to **generate the Embark change log**.
|
||||
|
||||
### Commit Message Format
|
||||
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||
format that includes a **type**, a **scope** and a **subject**:
|
||||
|
||||
```
|
||||
type(@embark|@cockpit/<SCOPE>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
The **header** is mandatory and the **scope** of the header is optional.
|
||||
|
||||
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
|
||||
to read on GitHub as well as in various git tools.
|
||||
|
||||
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
|
||||
|
||||
Samples:
|
||||
|
||||
```
|
||||
docs(changelog): update changelog to beta.5
|
||||
```
|
||||
```
|
||||
fix(release): need to depend on latest rxjs and zone.js
|
||||
|
||||
The version in our package.json gets copied to the one we publish, and users need the latest of these.
|
||||
```
|
||||
|
||||
### Revert
|
||||
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
|
||||
|
||||
### Type
|
||||
Must be one of the following:
|
||||
|
||||
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
* **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||
* **docs**: Documentation only changes
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **perf**: A code change that improves performance
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
### Scope
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.
|
||||
|
||||
The following is the list of supported scopes:
|
||||
|
||||
* **@embark/cli** - Embark command line interface
|
||||
* **@embark/core** - Embark core
|
||||
* **@embark/<`module`>** - Embark core module (any folder name under `packages/embark/src/lib/modules`), ie `@embark/console` or `@embark/debugger`
|
||||
* **@embark/<`package`>** - Embark package in the mono repo (any folder name under `packages`), ie `@embark/embarkjs` or `@embark/embark-compiler`
|
||||
* **@embark/<`section`>** - Cockpit site section, ie `@cockpit/explorer`
|
||||
|
||||
There are currently a few exceptions to the "use package name" rule:
|
||||
|
||||
* **packaging**: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc.
|
||||
* **changelog**: used for updating the release notes in CHANGELOG.md
|
||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
|
||||
|
||||
### Subject
|
||||
The subject contains a succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize the first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
### Body
|
||||
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
|
||||
The body should include the motivation for the change and contrast this with previous behavior.
|
||||
|
||||
### Footer
|
||||
The footer should contain any information about **Breaking Changes** and is also the place to
|
||||
reference GitHub issues that this commit **Closes**.
|
||||
|
||||
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
title: Creating apps with Embark
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Once Embark is installed, there are different ways to create a new decentralized application. Whether we intent to create an application that uses only Smart Contracts, or we want to take advantage of other decentralized features like storages and messaging, Embark provides options and templates for various scenarios. Let's take a look!
|
||||
|
||||
## Using the `demo` command
|
||||
|
||||
As discussed in our [quickstart guide](/docs/quick_start.html), the fastest way to get up and running with Embark is using its `demo` command. This will scaffold a new demo application and its needed environment in a folder called `embark_demo`. The demo application lets you play with Embark's APIs through a pre-built web interface.
|
||||
|
||||
```
|
||||
$ embark demo
|
||||
```
|
||||
|
||||
## Creating a new app
|
||||
|
||||
If you prefer starting entirely from scratch, while still getting a ready to use environment, Embark's `new` command has got you covered. Similar to the `demo` command, it will scaffold a new project folder. However, it will not include the demo application. The green field is all yours.
|
||||
|
||||
{% notification info 'Quick tip: Smart Contract only apps' %}
|
||||
Smart Contract developers that mainly want to focus on building and deploying Smart Contracts can take advantage of `new` command's `--contracts-only` option, as described [here](create_project.html#Creating-“contracts-only”-apps).
|
||||
{% endnotification %}
|
||||
|
||||
```
|
||||
$ embark new <YourDappName>
|
||||
```
|
||||
|
||||
## Creating apps from templates
|
||||
|
||||
Another possible scenario to start from is taking advantage of a template. Embark [comes with templates](/templates) for various environments and frameworks, but you can also use any template created by the community. In order to create a new app from a template, use the `--template` option and either specify a supported template name, or a Git host URL.
|
||||
|
||||
The following example creates a new application from [Embark's TypeScript template](https://github.com/embark-framework/embark-typescript-template):
|
||||
|
||||
```
|
||||
$ embark new <YourDAppName> --template typescript
|
||||
```
|
||||
|
||||
To learn more about supported templates, head over to our [templates](/templates) or look out for `embark-[template_name]-template` [repositories](https://github.com/embark-framework?utf8=%E2%9C%93&q=template&type=&language=).
|
||||
|
||||
Templates can also be fetched from external resources as long as they can be referred to via Git host URLs. The following example fetches a template from a GitHub repository and uses that to create that app:
|
||||
|
||||
```
|
||||
$ embark new <YourDAppName> --template https://github.com/embark-framework/embark-vue-template
|
||||
```
|
||||
|
||||
In fact, in case of GitHub, the same can be done with the username/repository shortcut:
|
||||
|
||||
```
|
||||
$ embark new <YourDAppName> --template embark-framework/embark-vue-template
|
||||
```
|
||||
|
||||
It is even possible to specify the branch by appending a `#` and the branch name you're interested in:
|
||||
|
||||
```
|
||||
$ embark new <YourDAppName> --template status-im/dappcon-workshop-dapp#start-here
|
||||
```
|
||||
|
||||
## Creating "contracts-only" apps
|
||||
|
||||
Sometimes, all we really want to do is creating, developing, compiling and deploying Smart Contracts without introducing an actual front-end that talks to them. Embark lets us scaffold apps that come with the most minimal setup needed to build and deploy our Smart Contracts, using the `--contracts-only` option.
|
||||
|
||||
The following command will create a project with all Embark services disabled except the blockchain service.
|
||||
|
||||
```
|
||||
$ embark new <YourDAppName> --contracts-only
|
||||
```
|
||||
|
||||
This will also affect the generated application structure, as Smart Contract only apps are less complex. Learn more about the application structure of Smart Contract only apps [here](structure.html#Simple-template-structure).
|
||||
|
||||
{% notification info 'A note on --simple' %}
|
||||
In earlier versions of Embark the same could be achieved using the `--simple` option. This option is still supported but will be deprecated and likely be removed in future versions of Embark.
|
||||
{% endnotification %}
|
|
@ -0,0 +1,40 @@
|
|||
title: Creating Plugins
|
||||
layout: docs
|
||||
---
|
||||
|
||||
If you can't [find a plugin](/plugins) that fulfills your needs, it's probably a good time to think about creating your own. In this guide we'll discuss how to set up a custom plugin and briefly talk about various use cases. Later on we'll dive into the available [Plugin APIs](/docs/plugin_reference.html).
|
||||
|
||||
## Creating a plugin project
|
||||
|
||||
A plugin is really just another NodeJS project that takes a plugin API object and make use of it. To get started, all we have to do is creating a new directory and initializing it as an npm module:
|
||||
|
||||
```
|
||||
$ mkdir yourpluginname
|
||||
$ cd yourpluginname
|
||||
$ npm init
|
||||
```
|
||||
|
||||
Once that is done we can create an `index.js` file that contains the following code:
|
||||
|
||||
```
|
||||
module.exports = function(embark) {
|
||||
// plugin logic goes here
|
||||
}
|
||||
```
|
||||
|
||||
The `embark` object provides plenty of APIs to extend different functionalities of Embark, which can be found in the [Plugin Api Reference](plugin_reference.html).
|
||||
|
||||
## Usecase examples
|
||||
|
||||
Since the Plugin API surface is rather big, here are some usecase examples to sparkle some inspiration:
|
||||
|
||||
* Adding a Smart Contract file to the list of source files to be watched so they can be used by other Smart Contracts with `addContractFile()`
|
||||
* Adding a Smart Contract configuration using `registerContractConfiguration()` (goes well with `addContractFile()`)
|
||||
* Adding a hook that's called before a Smart Contract's binary will be deployed using `beforeDeploy()`
|
||||
* Configure a custom provider for web3 initialization with `registerClientWeb3Provider()`
|
||||
* Create your own custom Smart Contract wrapper logic using `registerContractsGeneration()`
|
||||
* Adding a new command to Embark's interactive console using `registerConsoleCommand()`
|
||||
* Adding support for other compilers such as Viper, LLL, etc. using `embark.registerCompiler()`
|
||||
* Executing certain actions when Smart Contracts are deployed with `embark.events.on()`
|
||||
* Registering a service in Embark - `registerServiceCheck()`
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
title: Using the dashboard
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Embark provides a very useful dashboard view that aims to make it easier for developers to keep track of things, such as running services, compilation and deployments. In this guide, we'll take a closer look at the dashboard and its features.
|
||||
|
||||
## Overview
|
||||
|
||||
Embark spins up the dashboard view automatically for us, whenever we run `embark run` inside an Embark project. Here's what it looks like:
|
||||
|
||||
![Dashboard](/assets/images/embark-dashboard.png)
|
||||
|
||||
The dashboard is separated into different sections, each with their own responsibility. The sections are:
|
||||
|
||||
- **Contracts** - This section shows you not only all the Smart Contracts within your project, but also their status on whether they are being deployed at the moment or not. Notice that, as we make changes to our Smart Contracts, the dashboard will reflect the automatic redeployment in this section as well, making it very easy to stay on top of things.
|
||||
|
||||
- **Environment** - This is the name of the [environment](environments.html) we're running Embark in at the moment. If we don't specify an environment when running `embark run`, Embark will default to `development`.
|
||||
|
||||
- **Status** - As mentioned, Embark watches for changes in our application's source code and will recompile, rebuild and redeploy components accordingly. The status section tells us what status Embark is currently in. Which could be one of the following:
|
||||
|
||||
- **Compiling** - Compiles application's Smart Contracts
|
||||
- **Building** - Builds application's front-end
|
||||
- **Ready** - Ready and IDLE
|
||||
|
||||
- **Available Services** - This section displays all services available to our application. If a service is down or unreachable, it will show up in red.
|
||||
|
||||
- **Logs and Console** - While the logs section simply prints out all of Embark's output, the console can be used to either interact with our application's Smart Contracts or Embark itself. Use the `help` command to get a list of all available commands supported by Embark's console, or head over to our guide on [Using the the console](/docs/using_the_console.html) to learn more.
|
||||
|
||||
## Running without a dashboard
|
||||
|
||||
Embark can be run without spinning up the dashboard view using the `--nodashboard` option. Head over to our guide on [running apps](running_apps.html#Running-an-app-without-the-dashboard) for more information.
|
||||
|