Develop Your Ethereum Dapp with Eggjs(koa) & web3.js
Eggjs
Eggjs is Alibaba’s open source enterprise-level Node.js framework based on Koa2. eggjs is basically out-of-the-box, follows “Convention over Configuration”. In daily development, very smooth to use. And ecosystem is also relatively complete, koa2 plugins can all be integrated into framework.
Egg.js Directory Structure
egg-project
├── package.json
├── app.js (optional)
├── agent.js (optional)
├── app
|| ├── router.js
│ ├── controller
│ | └── home.js
│ ├── service (optional)
│ | └── user.js
│ ├── middleware (optional)
│ | └── response_time.js
│ ├── schedule (optional)
│ | └── my_task.js
│ ├── public (optional)
│ | └── reset.css
│ ├── view (optional)
│ | └── home.tpl
│ └── extend (optional)
│ ├── helper.js (optional)
│ ├── request.js (optional)
│ ├── response.js (optional)
│ ├── context.js (optional)
│ ├── application.js (optional)
│ └── agent.js (optional)
├── config
|| ├── plugin.js
|| ├── config.default.js
│ ├── config.prod.js
|| ├── config.test.js (optional)
|| ├── config.local.js (optional)
|| └── config.unittest.js (optional)
└── test
├── middleware
| └── response_time.test.js
└── controller
└── home.test.js
As above, directories agreed by framework:
app/router.jsUsed to configure URL routing rules.app/controller/**Used to parse user input, process and return corresponding results.app/service/**Used to write business logic layer, optional, recommend using.app/middleware/**Used to write middleware, optional.app/public/**Used to place static resources, optional, see built-in plugin egg-static.app/extend/**Used for framework extensions, optional.config/config.{env}.jsUsed to write configuration files.config/plugin.jsUsed to configure plugins to load.test/**Used for unit tests.app.jsandagent.jsUsed for custom startup initialization work, optional.
Directories agreed by built-in plugins:
app/public/**Used to place static resources, optional, see built-in plugin egg-static.app/schedule/**Used for scheduled tasks, optional.
If need to customize your own directory conventions, see Loader API
app/view/**Used to place template files, optional, agreed by template plugin.app/model/**Used to place domain models, optional, agreed by domain-related plugins, like egg-sequelize.
web3.js
To make your DAPP able to access blockchain data, one option is to use web3 object provided by web3.js. At underlying implementation, it communicates with local node through RPC calls. web3.js can connect to any blockchain node that exposes RPC interface.
Import Method
Custom startup method in egg.js
//app.js
"use strict";
const Web3 = require("web3");
module.exports = (app) => {
app.beforeStart(async () => {
const { config } = app;
let originWeb3;
// web3 initialization
if (typeof originWeb3 !== "undefined") {
console.warn(
"Using web3 detected from external source. If you find that your accounts don't appear or you have 0 MetaCoin, ensure you've configured that source properly. If using MetaMask, see the following link. Feel free to delete this warning. :) http://truffleframework.com/tutorials/truffle-and-metamask"
);
// Use Mist/MetaMask's provider
originWeb3 = new Web3(originWeb3.currentProvider);
} else {
console.warn(
`No web3 detected. Falling back to http://${config.web3.host}:${config.web3.port}. You should remove this fallback when you deploy live, as it\'s inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask`
);
// fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
originWeb3 = new Web3(
new Web3.providers.HttpProvider(
`http://${config.web3.host}:${config.web3.port}`
)
);
}
// Mount web3js to app
app.web3 = originWeb3;
// Create a context to call service methods
const ctx = app.createAnonymousContext();
app.cities = await ctx.service.connection.getAdmin();
});
};
Service Layer Calling Smart Contracts
// app/service/
"use strict";
const Service = require("egg").Service;
const contract = require("truffle-contract");
// Import smart contract
const stock_artifact = require("../../build/contracts/Stock.json");
//Link contract
const Stock = contract(stock_artifact);
let adminAddress;
let meta;
class ConnectionService extends Service {
constructor(...args) {
super(...args);
this.web3 = this.app.web3;
}
setProvider() {
Stock.setProvider(this.web3.currentProvider);
// Fix apply error
if (typeof Stock.currentProvider.sendAsync !== "function") {
Stock.currentProvider.sendAsync = function () {
return Stock.currentProvider.send.apply(
Stock.currentProvider,
arguments
);
};
}
}
getAccounts() {
return new Promise((resolve, reject) => {
this.setProvider();
// Get the initial account balance so it can be displayed.
// Use web3.js to interact with smart contract
this.web3.eth.getAccounts(function (err, accs) {
if (err != null) {
console.log("There was an error fetching your accounts.");
reject("There was an error fetching your accounts.");
return;
}
if (accs.length === 0) {
console.log(
"Couldn't get any accounts! Make sure your Ethereum client is configured correctly."
);
reject(
"Couldn't get any accounts! Make sure your Ethereum client is configured correctly."
);
return;
}
this.accounts = accs;
resolve(this.accounts);
});
});
}
getBalance(account) {
return new Promise((resolve, reject) => {
this.setProvider();
Stock.deployed()
.then(function (instance) {
meta = instance;
return meta.getBalance.call(account, { from: adminAddress });
})
.then(function (value) {
resolve({
shareLotsLength: value[0],
total: value[1],
});
})
.catch(function (e) {
console.log(e);
reject("Error 404");
});
});
}
// Add user
async addMember(address, name, empNo) {
if (!this.adminAddress) {
await this.getAdmin();
}
this.setProvider();
return new Promise((resolve, reject) => {
Stock.deployed()
.then((instance) => {
meta = instance;
return meta.addMember(address, name, Number(empNo), {
from: adminAddress || "0x0000000000000000000000000000000000000000",
});
})
.then((value) => {
let args = "not found";
for (let i = 0; i < value.logs.length; i++) {
const log = value.logs[i];
if (log.event === "addmember") {
args = log.args;
break;
}
}
resolve(args);
})
.catch((e) => {
console.log(e);
reject("add Member error");
});
});
}
// Get user
getAdmin() {
return new Promise((resolve, reject) => {
this.setProvider();
Stock.deployed()
.then((instance) => {
return instance.getAdmin.call();
})
.then((value) => {
adminAddress = value;
resolve(value);
})
.catch((e) => {
console.log(e);
reject("getAdmin error");
});
});
}
}
module.exports = ConnectionService;
Complete Demo: https://github.com/Num142857/egg-block-chain
Demo Usage Method
Globally Install truffle
npm install -g truffle
Smart Contract
# Migrate
truffle migrate
#Compile contract
truffle compile
Local Development
$ npm i
$ npm run dev
$ open http://localhost:7001/
Deploy
$ npm start
$ npm stop