diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000..0dc4a9c
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,8 @@
+# Usage
+0. set the `appId` (Fluence) and `lazyAddress` (Eth contract) in `index.js`
+
+1. run ```npm i```
+
+2. run ```npm run build```
+
+3. run ```npm run start```
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..22551f9
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,95 @@
+
+
+
+
+
+ Lazy snark dashboard
+
+
+
+
+
+
+
+
+
+ Lazy snark dashboard
+
+
+
+
+
+ State ID's
+
+
+ Fluence status
+
+
+ Challenge?
+
+
+
+
+ None
+
+
+ None
+
+
+
Challenge on Fluence!
+
+
+
+
+
+ None
+
+
+ None
+
+
+
Challenge on Fluence!
+
+
+
+
+
+ None
+
+
+ None
+
+
+
Challenge on Fluence!
+
+
+
+
+
+
+ None
+
+
+ None
+
+
+
Challenge on Fluence!
+
+
+
+
+
+ None
+
+
+ None
+
+
+
Challenge on Fluence!
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/index.js b/frontend/index.js
new file mode 100644
index 0000000..bab5bc2
--- /dev/null
+++ b/frontend/index.js
@@ -0,0 +1,437 @@
+//CONNECT FLUENCE
+import * as fluence from "fluence";
+
+
+window.getResultAsString = function (result) {
+ return result.result().then((r) => r.asString())
+};
+
+var contractInstance;
+
+$(document).ready(function() {
+ let contractAddress = "0xeFF91455de6D4CF57C141bD8bF819E5f873c1A01";
+
+ // set ethUrl to `undefined` to use MetaMask instead of Ethereum node
+ let ethUrl = "http://rinkeby.fluence.one:8545/";
+
+ // application to interact with that stored in Fluence contract
+ let appId = "264";
+
+ // create a session between client and backend application, and then join the game
+ fluence.connect(contractAddress, appId, ethUrl).then((s) => {
+ console.log("Session created");
+ window.session = s;
+ });
+
+ var lazyAddress = '0x1cca1f0be338c747b11a16aba8d0905251628bdf';
+ let ControllerAbi = [
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "verifier",
+ "outputs": [
+ {
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "stake",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "tasks",
+ "outputs": [
+ {
+ "components": [
+ {
+ "name": "input",
+ "type": "uint256[5]"
+ }
+ ],
+ "name": "data",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ {
+ "name": "a",
+ "type": "uint256[2]"
+ },
+ {
+ "name": "b",
+ "type": "uint256[2][2]"
+ },
+ {
+ "name": "c",
+ "type": "uint256[2]"
+ }
+ ],
+ "name": "proof",
+ "type": "tuple"
+ },
+ {
+ "name": "submitter",
+ "type": "address"
+ },
+ {
+ "name": "timestamp",
+ "type": "uint256"
+ },
+ {
+ "name": "status",
+ "type": "uint8"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "components": [
+ {
+ "name": "input",
+ "type": "uint256[5]"
+ }
+ ],
+ "name": "data",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ {
+ "name": "a",
+ "type": "uint256[2]"
+ },
+ {
+ "name": "b",
+ "type": "uint256[2][2]"
+ },
+ {
+ "name": "c",
+ "type": "uint256[2]"
+ }
+ ],
+ "name": "proof",
+ "type": "tuple"
+ },
+ {
+ "name": "submitter",
+ "type": "address"
+ },
+ {
+ "name": "timestamp",
+ "type": "uint256"
+ },
+ {
+ "name": "status",
+ "type": "uint8"
+ }
+ ],
+ "indexed": false,
+ "name": "task",
+ "type": "tuple"
+ }
+ ],
+ "name": "Submitted",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "challenger",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "index",
+ "type": "uint256"
+ }
+ ],
+ "name": "Challenged",
+ "type": "event"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "tasksNum",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "components": [
+ {
+ "name": "input",
+ "type": "uint256[5]"
+ }
+ ],
+ "name": "data",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ {
+ "name": "a",
+ "type": "uint256[2]"
+ },
+ {
+ "name": "b",
+ "type": "uint256[2][2]"
+ },
+ {
+ "name": "c",
+ "type": "uint256[2]"
+ }
+ ],
+ "name": "proof",
+ "type": "tuple"
+ }
+ ],
+ "name": "submit",
+ "outputs": [],
+ "payable": true,
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "id",
+ "type": "uint256"
+ }
+ ],
+ "name": "challenge",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "id",
+ "type": "uint256"
+ }
+ ],
+ "name": "finzalize",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "id",
+ "type": "uint256"
+ }
+ ],
+ "name": "taskDataById",
+ "outputs": [
+ {
+ "name": "data",
+ "type": "uint256[13]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "last5Timestamps",
+ "outputs": [
+ {
+ "name": "result",
+ "type": "uint256[5]"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "id",
+ "type": "uint256"
+ }
+ ],
+ "name": "getDataById",
+ "outputs": [
+ {
+ "components": [
+ {
+ "components": [
+ {
+ "name": "input",
+ "type": "uint256[5]"
+ }
+ ],
+ "name": "data",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ {
+ "name": "a",
+ "type": "uint256[2]"
+ },
+ {
+ "name": "b",
+ "type": "uint256[2][2]"
+ },
+ {
+ "name": "c",
+ "type": "uint256[2]"
+ }
+ ],
+ "name": "proof",
+ "type": "tuple"
+ },
+ {
+ "name": "submitter",
+ "type": "address"
+ },
+ {
+ "name": "timestamp",
+ "type": "uint256"
+ },
+ {
+ "name": "status",
+ "type": "uint8"
+ }
+ ],
+ "name": "task",
+ "type": "tuple"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ }
+ ];
+ let ControllerContract = web3.eth.contract(ControllerAbi);
+ contractInstance = ControllerContract.at(lazyAddress);
+ window.ethereum.enable();
+
+ contractInstance.tasksNum(function (err, result) {
+ let maxLen = Math.min(result, 5);
+ for (let i = 0; i < maxLen; i++) {
+ let data = result - 1 - i;
+ $('#state-id-' + i).text(data);
+ let fluenceResponse_check = session.request(`{"action": "Check", "proof_id": ${data}}`);
+ getResultAsString(fluenceResponse_check).then(function (str) {
+ let fluenceResponse = JSON.parse(str);
+ console.log(fluenceResponse);
+ if (fluenceResponse.hasOwnProperty('verifed')) {
+ if (fluenceResponse.verifed) {
+ // все хорошо - мы проверили в флюенсе
+ $('#state-status-fluence-' + i).text('TRUE by Fluence.');
+ $('#challenge-' + i).prop('disabled', true);
+ } else {
+ // мы проверили, пруф неправильный
+ $('#state-status-fluence-' + i).text('FALSE by Fluence.');
+ $('#challenge-' + i).text('Challenge on Ethereum!')
+ }
+ } else {
+ console.log('Task N ' + data + ' is not checked on Fluence!')
+ }
+ });
+ }
+ });
+});
+
+$('button').click(function () {
+ let id = $(this)[0].id.slice(-1);
+ let data = $('#state-id-' + id)[0].textContent;
+
+ if ($(this)[0].textContent === 'Challenge on Fluence!') {
+ contractInstance.taskDataById(data, function (err, result) {
+ let public_par = result.slice(0, 5);
+ let proof = result.slice(5, 13);
+
+ let fluenceResponse_ver = session.request(`{"action": "Verify", "proof_id": ${data}, "public_par": [${public_par}], "proof": [${proof}]}`);
+ getResultAsString(fluenceResponse_ver).then(function (str) {
+ let fluenceResponse = JSON.parse(str);
+ let success = fluenceResponse.result === 1;
+ console.log(fluenceResponse);
+ if (success) {
+ // все хорошо - мы проверили в флюенсе
+ $('#state-status-fluence-' + id).text('TRUE by Fluence.');
+ $('#challenge-' + id).prop('disabled', true);
+ } else {
+ // мы проверили, пруф неправильный
+ $('#state-status-fluence-' + id).text('FALSE by Fluence.');
+ $('#challenge-' + id).text('Challenge on Ethereum!')
+ }
+ });
+ });
+ } else if ($(this)[0].textContent === 'Challenge on Ethereum!') {
+ challengeEthereum(id, data);
+ } else {
+
+ }
+});
+
+function challengeEthereum(id, data) {
+ console.log('Challenging task N ' + data + ' on Ethereum!');
+ contractInstance.challenge.sendTransaction(data, function (err, txHash) {
+ $('#challenge-' + id).remove();
+ $('#link-' + id).text('See tx on Etherscan!').attr("href", "https://rinkeby.etherscan.io/tx/" + txHash);
+
+ });
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..337fe53
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "frontend-challenger",
+ "version": "1.0.0",
+ "description": "",
+ "private": true,
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "webpack-dev-server",
+ "build": "webpack"
+ },
+ "files": [
+ "index.html"
+ ],
+ "keywords": [],
+ "author": "",
+ "devDependencies": {
+ "copy-webpack-plugin": "^5.0.2",
+ "css-loader": "^2.1.1",
+ "html-webpack-plugin": "^3.2.0",
+ "style-loader": "0.23.1",
+ "webpack": "^4.29.6",
+ "webpack-cli": "^3.3.0",
+ "webpack-dev-server": "^3.2.1",
+ "web3": "1.0.0-beta.55"
+ },
+ "dependencies": {
+ "Package": "0.0.1",
+ "fluence": "0.1.26"
+ }
+}
+
diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js
new file mode 100644
index 0000000..e755938
--- /dev/null
+++ b/frontend/webpack.config.js
@@ -0,0 +1,35 @@
+const path = require('path');
+const webpack = require('webpack');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+
+module.exports = {
+ // use index.js as entrypoint
+ entry: {
+ app: ['./index.js']
+ },
+ devServer: {
+ contentBase: './bundle',
+ hot: true
+ },
+ mode: "production",
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader']
+ }
+ ]
+ },
+ // build all code in `bundle.js` in `bundle` directory
+ output: {
+ filename: 'bundle.js',
+ path: path.resolve(__dirname, 'bundle')
+ },
+ plugins: [
+ // create `index.html` with imported `bundle.js`
+ new CopyWebpackPlugin([{
+ from: './*.html'
+ }]),
+ new webpack.HotModuleReplacementPlugin()
+ ]
+};
diff --git a/truffle/contracts/Lazy.sol b/truffle/contracts/Lazy.sol
index 404b215..87cf7af 100644
--- a/truffle/contracts/Lazy.sol
+++ b/truffle/contracts/Lazy.sol
@@ -19,6 +19,9 @@ contract Lazy is Structs {
}
Task[] public tasks;
+ function tasksNum() external view returns(uint) {
+ return tasks.length;
+ }
uint256 public stake;
IVerifier public verifier;
@@ -65,6 +68,28 @@ contract Lazy is Structs {
msg.sender.transfer(stake);
}
+ function taskDataById(uint id) external view returns(
+ uint[13] memory data
+ ) {
+ Task memory task = tasks[id];
+
+ data[0] = task.data.input[0];
+ data[1] = task.data.input[1];
+ data[2] = task.data.input[2];
+ data[3] = task.data.input[3];
+ data[4] = task.data.input[4];
+
+ data[5] = task.proof.a[0];
+ data[6] = task.proof.a[1];
+
+ data[7] = task.proof.b[0][0];
+ data[8] = task.proof.b[0][1];
+ data[9] = task.proof.b[1][0];
+ data[10] = task.proof.b[1][0];
+
+ data[11] = task.proof.c[0];
+ data[12] = task.proof.c[1];
+ }
function last5Timestamps() view external returns (uint256[5] memory result) {