initial commit

Add init, deposit, send console commands to interact with the Plasma chain

Add plasma exit command and function

working in embark and browser

Remove browser codeAll browser code is now required through embarkjs-omg
This commit is contained in:
emizzle 2019-05-30 20:00:31 +07:00
commit 2a8f0bd937
No known key found for this signature in database
GPG Key ID: 1FD4BAB3C37EE9BA
12 changed files with 6581 additions and 0 deletions

31
.babelrc.js Normal file
View File

@ -0,0 +1,31 @@
/* global module require */
const cloneDeep = require('lodash.clonedeep');
module.exports = (api) => {
const env = api.env();
const base = {};
const browser = cloneDeep(base);
Object.assign(browser, {
ignore: [
'**/node.js'
]
});
const node = cloneDeep(base);
const nodeTest = cloneDeep(base);
switch (env) {
case 'browser':
return browser;
case 'node':
return node;
// case 'node:test':
// return nodeTest;
default:
return base;
}
};

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
dist
package
.idea
.vscode
.eslintrc.json
embark-omg-*.tgz
NOTES
npm-debug.log
TODO
yarn-error.log

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
engine-strict = true
save-exact = true

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Iuri Matias
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.

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# Embark-Omg Setup
Embark-omg is a plugin for [Embark](https://github.com/embark-framework/embark) that ...
#### Please report any other issues you find, thank you!

62
babel.config.js Normal file
View File

@ -0,0 +1,62 @@
/* global module require */
const cloneDeep = require('lodash.clonedeep');
module.exports = (api) => {
const env = api.env();
const base = {
babelrcRoots: [
'.',
'packages/*'
],
plugins: [
'babel-plugin-macros',
['@babel/plugin-proposal-decorators', {
legacy: true
}],
'@babel/plugin-syntax-dynamic-import',
['@babel/plugin-proposal-class-properties', {
loose: true
}],
'@babel/plugin-proposal-optional-chaining',
['@babel/plugin-transform-runtime', {
corejs: 2
}]
],
presets: [
'@babel/preset-env'
]
};
if (env === 'base' || env.startsWith('base:')) {
return base;
}
const browser = cloneDeep(base);
browser.plugins[browser.plugins.length - 1][1].useESModules = true;
browser.presets[0] = [browser.presets[0], {
modules: false,
targets: { browsers: ['last 1 version', 'not dead', '> 0.2%'] }
}];
if (env === 'browser' || env.startsWith('browser:')) {
return browser;
}
const node = cloneDeep(base);
node.plugins.splice(
node.plugins.indexOf('@babel/plugin-syntax-dynamic-import') + 1,
0,
'babel-plugin-dynamic-import-node'
);
node.presets[0] = [node.presets[0], {
targets: { node: '8.11.3' }
}];
if (env === 'node' || env.startsWith('node:')) {
return node;
}
return {};
};

75
package.json Normal file
View File

@ -0,0 +1,75 @@
{
"name": "embark-omg",
"version": "1.0.0",
"description": "OmiseGO plugin for Embark",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"lint": "./node_modules/.bin/eslint src/",
"babel": "cross-env BABEL_ENV=node babel --out-dir dist src --source-maps",
"build": "npm-run-all build:**",
"build:node": "cross-env BABEL_ENV=node babel src --extensions \".js\" --out-dir dist --source-maps",
"// build:node:test": "cross-env BABEL_ENV=node:test babel test --extensions \".js\" --out-dir build-test --source-maps",
"ci": "npm run qa",
"clean": "npm run reset",
"package": "npm pack",
"qa": "npm-run-all build test package",
"reset": "npx rimraf .nyc_output build-test coverage dist embark-omg-*.tgz package",
"start": "npm run watch",
"// test": "nyc --reporter=html --reporter=json mocha \"build-test/**/*.js\" --exit --no-timeouts --require source-map-support/register",
"watch": "run-p \"build:** -- --verbose --watch\""
},
"repository": {
"type": "git",
"url": "git+https://github.com/emizzle/embark-omg.git"
},
"keywords": [
"Embark",
"OmiseGO",
"blockchain",
"Ethereum"
],
"author": "eric.mastro@gmail.com",
"license": "ISC",
"bugs": {
"url": "https://github.com/emizzle/embark-omg/issues"
},
"homepage": "https://github.com/emizzle/embark-omg#readme",
"dependencies": {
"@babel/plugin-proposal-optional-chaining": "7.2.0",
"@babel/runtime-corejs2": "7.3.1",
"@omisego/omg-js": "1.2.2",
"@omisego/omg-js-childchain": "1.2.1",
"@omisego/omg-js-rootchain": "1.2.2",
"@omisego/omg-js-util": "1.2.1",
"async": "3.0.1",
"axios": "0.19.0",
"date-fns": "2.0.0-alpha.27",
"embark-utils": "^4.1.0-beta.2",
"embarkjs-omg": "1.0.0",
"ethers": "4.0.28"
},
"devDependencies": {
"@babel/cli": "7.2.3",
"@babel/core": "7.2.2",
"@babel/plugin-proposal-class-properties": "7.4.4",
"@babel/plugin-proposal-decorators": "7.4.4",
"@babel/plugin-syntax-dynamic-import": "7.2.0",
"@babel/plugin-transform-runtime": "7.4.0",
"@babel/preset-env": "7.4.1",
"babel-plugin-dynamic-import-node": "2.2.0",
"babel-plugin-macros": "2.6.0",
"cross-env": "5.2.0",
"eslint": "4.19.1",
"lodash.clonedeep": "4.5.0",
"npm-run-all": "4.1.5",
"rimraf": "2.6.2"
},
"engines": {
"node": ">=8.12.0 <12.0.0",
"npm": ">=6.4.1",
"yarn": ">=1.12.3"
}
}

276
src/index.js Normal file
View File

@ -0,0 +1,276 @@
import EmbarkJSOmg from "embarkjs-omg";
import EmbarkUtils from "./utils/embark";
import { dappPath } from "embark-utils";
import { formatDate } from "./utils";
import { getWatcherStatus } from "./utils/plasma";
import { waterfall } from "async";
// Service check constants
const SERVICE_CHECK_ON = 'on';
const SERVICE_CHECK_OFF = 'off';
/**
* Plugin that allows Embark to connect to and interact with an existing Plama chain,
* and provides an EmbarkJS.Plasma API to allow the DApp to interact with the chain.
*/
class EmbarkOmg extends EmbarkJSOmg {
constructor(embark) {
super(embark);
this.embark = embark;
this.events = embark.events;
this.pluginConfig = embark.pluginConfig;
this.accounts = [];
// gets hydrated blockchain config from embark, use it to init
this.events.once('config:load:blockchain', (blockchainConfig) => {
this.logger.info("blockchain config loaded...");
this.embarkUtils = new EmbarkUtils({ events: this.events, logger: this.logger, blockchainConfig });
this.init().then(() => {
this.addCodeToEmbarkJs();
});
});
this.registerServiceCheck();
this.registerConsoleCommands();
}
async init() {
// init account used for root and child chains
try {
const accounts = await this.embarkUtils.accounts;
this.accounts = accounts;
} catch (e) {
return this.logger.error(`Error getting accounts from Embark's config: ${e}`);
}
try {
this.web3Path = await this.embarkUtils.web3Path;
}
catch (e) {
this.logger.error(`Error getting web3 from Embark: ${e}`);
}
try {
await super.init(this.accounts, this.web3Path);
this.events.emit("embark-omg:init");
}
catch (e) {
this.logger.error(`Error initializing EmbarkOmg: ${e}`);
}
}
generateSymlink(varName, location) {
return new Promise((resolve, reject) => {
this.events.request('code-generator:symlink:generate', location, varName, (err, symlinkDest) => {
if (err) {
return reject(err);
}
resolve(symlinkDest);
});
});
}
codeGeneratorReady() {
return new Promise((resolve, _reject) => {
this.events.request('code-generator:ready', () => {
resolve();
});
});
}
async addCodeToEmbarkJs() {
const nodePath = dappPath('node_modules');
const embarkjsOmgPath = require.resolve("embarkjs-omg", { paths: [nodePath] });
let web3SymlinkPath, embarkJsOmgSymlinkPath;
await this.codeGeneratorReady();
// create a symlink to web3 - this is currently the job of the web3 connector, so either this will run
// first or the connector will overwrite it.
try {
web3SymlinkPath = await this.generateSymlink('web3', this.web3Path);
}
catch (err) {
this.logger.error(__('Error creating a symlink to web3'));
return this.logger.error(err.message || err);
}
try {
embarkJsOmgSymlinkPath = await this.generateSymlink('embarkjs-omg', embarkjsOmgPath);
}
catch (err) {
this.logger.error(__('Error creating a symlink to embarkjs-omg'));
return this.logger.error(err.message || err);
}
this.events.emit('runcode:register', 'embarkjsOmg', require('embarkjs-omg'), () => {
let code = "";
code += `\nlet __embarkPlasma = global.embarkjsOmg || require('${embarkJsOmgSymlinkPath}').default;`;
//code += `\nWeb3 = global.embarkjsOmg || require('${embarkJsOmgSymlinkPath}').default;`;
code += `\nconst opts = {
logger: {
info: console.log,
error: console.error,
trace: console.trace
},
pluginConfig: ${JSON.stringify(this.pluginConfig)}
};`;
code += "\nEmbarkJS.onReady(() => {";
code += "\n EmbarkJS.Plasma = new __embarkPlasma(opts);";
// code += `\n EmbarkJS.Plasma.init(${JSON.stringify(this.accounts)}, "${web3SymlinkPath}");`;
code += `\n EmbarkJS.Plasma.init(${JSON.stringify(this.accounts)}, global.embarkjsOmg ? "${web3SymlinkPath}" : null);`; // pass the symlink path ONLY when we are in the node (VM) context
code += "\n});";
this.embark.addCodeToEmbarkJS(code);
});
}
registerConsoleCommands() {
this.embark.registerConsoleCommand({
description: `Initialises the Plasma chain using the account configured in the DApp's blockchain configuration. All transactions on the child chain will use this as the 'from' account.`,
matches: ["plasma init", "plasma init --force"],
usage: "plasma init [--force]",
process: (cmd, callback) => {
const force = cmd.endsWith("--force");
if (this.inited && !force) {
return callback("The Plasma chain is already initialized. If you'd like to reinitialize the chain, use the --force option ('plasma init --force')."); // passes a message back to cockpit console
}
this.init()
.then((message) => {
callback(null, message);
})
.catch(callback);
}
});
const depositRegex = /^plasma[\s]+deposit[\s]+([0-9]+)$/;
this.embark.registerConsoleCommand({
description: "Deposits ETH from the root chain (Rinkeby) to the Plasma chain to be used for transacting on the Plasma chain.",
matches: (cmd) => {
return depositRegex.test(cmd);
},
usage: "plasma deposit [amount]",
process: (cmd, callback) => {
if (!this.inited) {
return callback("The Plasma chain has not been initialized. Please initialize the Plamsa chain using 'plasma init' before continuting."); // passes a message back to cockpit console
}
const matches = cmd.match(depositRegex) || [];
if (matches.length <= 1) {
return callback("Invalid command format, please use the format 'plasma deposit [amount]', ie 'plasma deposit 100000'");
}
this.deposit(matches[1])
.then((message) => {
callback(null, message);
})
.catch(callback);
}
});
const sendRegex = /^plasma[\s]+send[\s]+(0x[0-9,a-f,A-F]{40,40})[\s]+([0-9]+)$/;
this.embark.registerConsoleCommand({
description: "Sends an ETH tx on the Plasma chain from the account configured in the DApp's blockchain configuration to any other account on the Plasma chain.",
matches: (cmd) => {
return sendRegex.test(cmd);
},
usage: "plasma send [to_address] [amount]",
process: (cmd, callback) => {
if (!this.inited) {
return callback("The Plasma chain has not been initialized. Please initialize the Plamsa chain using 'plasma init' before continuting."); // passes a message back to cockpit console
}
const matches = cmd.match(sendRegex) || [];
if (matches.length <= 2) {
return callback("Invalid command format, please use the format 'plasma send [to_address] [amount]', ie 'plasma send 0x38d5beb778b6e62d82e3ba4633e08987e6d0f990 555'");
}
this.send(matches[1], matches[2])
.then((message) => {
callback(null, message);
})
.catch(callback);
}
});
const exitRegex = /^plasma[\s]+exit[\s]+(0x[0-9,a-f,A-F]{40,40})$/;
this.embark.registerConsoleCommand({
description: "Exits the ETH from the Plasma chain to the Rinkeby chain.",
matches: (cmd) => {
return exitRegex.test(cmd);
},
usage: "plasma exit [plasma_chain_address]",
process: (cmd, callback) => {
if (!this.inited) {
return callback("The Plasma chain has not been initialized. Please initialize the Plamsa chain using 'plasma init' before continuting."); // passes a message back to cockpit console
}
const matches = cmd.match(exitRegex) || [];
if (matches.length <= 1) {
return callback("Invalid command format, please use the format 'plasma exit [plasma_chain_address]', ie 'plasma exit 0x38d5beb778b6e62d82e3ba4633e08987e6d0f990'");
}
this.exit(matches[1]).then((message) => {
callback(null, message);
}).catch((e) => {
callback(e.message);
});
}
});
this.embark.registerConsoleCommand({
description: "Gets the status of the Plasma chain.",
matches: ["plasma status"],
process: (cmd, callback) => {
getWatcherStatus(this.pluginConfig.WATCHER_URL).then((status) => {
callback(null, JSON.stringify(status));
}).catch((e) => {
callback(e.message);
});
}
});
}
/**
* Registers this plugin for Embark service checks and sets up log messages for
* connection and disconnection events. The service check pings the Status app.
*
* @returns {void}
*/
registerServiceCheck() {
const name = "OMG Plasma Chain";
this.events.request("services:register", name, (cb) => {
waterfall([
(next) => {
if (this.inited) {
return next();
}
this.events.once("embark-omg:init", next);
},
(next) => {
// TODO: web3_clientVersion method is currently not implemented in web3.js 1.0
getWatcherStatus(this.pluginConfig.WATCHER_URL)
.then((status) => {
const serviceStatus = `Last block: ${formatDate(status.last_mined_child_block_timestamp)}`;
next(null, { name: serviceStatus, status: status ? SERVICE_CHECK_ON : SERVICE_CHECK_OFF });
})
.catch(next);
}
], (err, statusObj) => {
if (err) {
return cb(err);
}
cb(statusObj);
});
}, 5000, 'off');
this.events.on('check:backOnline:OmiseGO', () => {
this.logger.info("------------------");
this.logger.info("Connected to the Plama chain!");
this.logger.info("------------------");
});
this.events.on('check:wentOffline:OmiseGO', () => {
this.logger.error("------------------");
this.logger.error("Couldn't connect or lost connection to the Plasma chain...");
this.logger.error("------------------");
});
}
}
export default EmbarkOmg;

42
src/utils/embark/index.js Normal file
View File

@ -0,0 +1,42 @@
import { AccountParser, dappPath, embarkPath } from "embark-utils";
export default class EmbarkUtils {
constructor({ events, logger, blockchainConfig }) {
this.events = events;
this.logger = logger;
this.blockchainConfig = blockchainConfig;
}
get accounts() {
return new Promise((resolve, reject) => {
this.events.request("blockchain:ready", () => {
this.events.request("blockchain:get", (embarkWeb3) => {
try {
const accountsParsed = AccountParser.parseAccountsConfig(this.blockchainConfig.accounts, embarkWeb3, dappPath(), this.logger, []);
resolve(accountsParsed);
} catch (e) {
reject(e);
}
});
});
});
}
get web3Path() {
return new Promise((resolve, reject) => {
this.events.request("version:get:web3", (web3Version) => {
if (web3Version === "1.0.0-beta") {
const nodePath = embarkPath('node_modules');
const web3Path = require.resolve("web3", { paths: [nodePath] });
return resolve(web3Path);
}
this.events.request("version:getPackageLocation", "web3", web3Version, (err, location) => {
if (err) {
return reject(err);
}
const locationPath = embarkPath(location).replace(/\\/g, '/');
resolve(locationPath);
});
});
});
}
}

14
src/utils/index.js Normal file
View File

@ -0,0 +1,14 @@
import { formatDistance } from 'date-fns';
/**
* Formats an int date into a displayable date
* @param {Number} intDate - date in seconds
* @returns {String} prettyfied date
*/
export function formatDate(intDate) {
const padZeros = 13 - intDate.toString().length;
if (padZeros > 0) {
intDate *= Math.pow(10, padZeros);
}
return formatDistance(new Date(intDate), new Date()) + ' ago';
}

View File

@ -0,0 +1,9 @@
import { post } from "axios";
export async function getWatcherStatus(watcherUrl) {
const response = await post(`${watcherUrl}/status.get`);
if (!(response.status === 200 && response.data && response.data.success)) {
throw new Error(`Error getting status of the Plasma watcher`);
}
return response.data.data;
}

6028
yarn.lock Normal file

File diff suppressed because it is too large Load Diff