├── .gitattribute ├── .gitignore ├── BouncerProxy ├── BouncerProxy.sol ├── arguments.js └── dependencies.js ├── Example ├── Example.compiled ├── Example.sol ├── arguments.js └── dependencies.js ├── LICENSE ├── README.md ├── SomeToken ├── SomeToken.sol ├── arguments.js └── dependencies.js ├── attach.sh ├── backend ├── index.js ├── modules │ ├── contractLoader.js │ ├── eventParser.js │ └── liveParser.js ├── redis.sh ├── run.sh └── simple.js ├── clevis.json ├── contracts.clevis ├── dev.sh ├── docker ├── Dockerfile ├── attach.sh ├── bootstrap.sh ├── build.sh ├── deploy.sh ├── package.json └── run.sh ├── notes.txt ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── run.sh ├── src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── allBouncers.js │ ├── backend.js │ ├── bouncer.js │ ├── miner.js │ └── owner.js ├── images │ ├── qr.png │ └── videopreview.jpg ├── index.css └── index.js ├── stop.sh ├── tests ├── clevis.js ├── compile.js ├── deploy.js ├── example.js ├── fast.js ├── full.js ├── metamask.js ├── publish.js ├── test.js └── version.js └── yarn.lock /.gitattribute: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | backend/ipfs 2 | src/contracts/* 3 | public/reload.txt 4 | deploy.network 5 | aws.json 6 | backend/redisdata 7 | accounts.json 8 | **.DS_* 9 | openzeppelin-solidity 10 | zeppelin-solidity 11 | package-lock.json 12 | *.log 13 | *.ens 14 | */*/*.abi 15 | */*/*.bytecode 16 | */*/*.address 17 | */*/*.compiled 18 | */*/*.run.xml 19 | */*/*.log.* 20 | */*/*.blockNumber 21 | */*.abi 22 | */*.bytecode 23 | */*.bib 24 | */*.address 25 | */*.compiled 26 | */*.run.xml 27 | */*.log.* 28 | */*.blockNumber 29 | */.clevis 30 | */*/.clevis 31 | */node_modules 32 | /node_modules 33 | 34 | openzeppelin-solidity 35 | 36 | 37 | # Logs 38 | logs 39 | *.log 40 | npm-debug.log* 41 | yarn-debug.log* 42 | yarn-error.log* 43 | 44 | # Runtime data 45 | pids 46 | *.pid 47 | *.seed 48 | *.pid.lock 49 | 50 | # Directory for instrumented libs generated by jscoverage/JSCover 51 | lib-cov 52 | 53 | # Coverage directory used by tools like istanbul 54 | coverage 55 | 56 | # nyc test coverage 57 | .nyc_output 58 | 59 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 60 | .grunt 61 | 62 | # Bower dependency directory (https://bower.io/) 63 | bower_components 64 | 65 | # node-waf configuration 66 | .lock-wscript 67 | 68 | # Compiled binary addons (https://nodejs.org/api/addons.html) 69 | build/Release 70 | 71 | # Dependency directories 72 | node_modules/ 73 | jspm_packages/ 74 | 75 | # TypeScript v1 declaration files 76 | typings/ 77 | 78 | # Optional npm cache directory 79 | .npm 80 | 81 | # Optional eslint cache 82 | .eslintcache 83 | 84 | # Optional REPL history 85 | .node_repl_history 86 | 87 | # Output of 'npm pack' 88 | *.tgz 89 | 90 | # Yarn Integrity file 91 | .yarn-integrity 92 | 93 | # dotenv environment variables file 94 | .env 95 | 96 | # next.js build output 97 | .next 98 | # See https://help.github.com/ignore-files/ for more about ignoring files. 99 | 100 | # dependencies 101 | /node_modules 102 | 103 | # testing 104 | /coverage 105 | 106 | # production 107 | /build 108 | 109 | # misc 110 | .DS_Store 111 | .env.local 112 | .env.development.local 113 | .env.test.local 114 | .env.production.local 115 | 116 | npm-debug.log* 117 | yarn-debug.log* 118 | yarn-error.log* 119 | -------------------------------------------------------------------------------- /BouncerProxy/BouncerProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | Bouncer identity proxy that executes meta transactions for etherless accounts. 5 | 6 | Purpose: 7 | I wanted a way for etherless accounts to transact with the blockchain through an identity proxy without paying gas. 8 | I'm sure there are many examples of something like this already deployed that work a lot better, this is just me learning. 9 | (I would love feedback: https://twitter.com/austingriffith) 10 | 11 | 1) An etherless account crafts a meta transaction and signs it 12 | 2) A (properly incentivized) relay account submits the transaction to the BouncerProxy and pays the gas 13 | 3) If the meta transaction is valid AND the etherless account is a valid 'Bouncer', the transaction is executed 14 | (and the sender is paid in arbitrary tokens from the signer) 15 | 16 | Inspired by: 17 | @avsa - https://www.youtube.com/watch?v=qF2lhJzngto found this later: https://github.com/status-im/contracts/blob/73-economic-abstraction/contracts/identity/IdentityGasRelay.sol 18 | @mattgcondon - https://twitter.com/mattgcondon/status/1022287545139449856 && https://twitter.com/mattgcondon/status/1021984009428107264 19 | @owocki - https://twitter.com/owocki/status/1021859962882908160 20 | @danfinlay - https://twitter.com/danfinlay/status/1022271384938983424 21 | @PhABCD - https://twitter.com/PhABCD/status/1021974772786319361 22 | gnosis-safe 23 | uport-identity 24 | 25 | */ 26 | 27 | 28 | //use case 1: 29 | //you deploy the bouncer proxy and use it as a standard identity for your own etherless accounts 30 | // (multiple devices you don't want to store eth on or move private keys to will need to be added as Bouncers) 31 | //you run your own relayer and the rewardToken is 0 32 | 33 | //use case 2: 34 | //you deploy the bouncer proxy and use it as a standard identity for your own etherless accounts 35 | // (multiple devices you don't want to store eth on or move private keys to will need to be added as Bouncers) 36 | // a community if relayers are incentivized by the rewardToken to pay the gas to run your transactions for you 37 | //SEE: universal logins via @avsa 38 | 39 | //use case 3: 40 | //you deploy the bouncer proxy and use it to let third parties submit transactions as a standard identity 41 | // (multiple developer accounts will need to be added as Bouncers to 'whitelist' them to make meta transactions) 42 | //you run your own relayer and pay for all of their transactions, revoking any bad actors if needed 43 | //SEE: GitCoin (via @owocki) wants to pay for some of the initial transactions of their Developers to lower the barrier to entry 44 | 45 | //use case 4: 46 | //you deploy the bouncer proxy and use it to let third parties submit transactions as a standard identity 47 | // (multiple developer accounts will need to be added as Bouncers to 'whitelist' them to make meta transactions) 48 | //you run your own relayer and pay for all of their transactions, revoking any bad actors if needed 49 | 50 | contract BouncerProxy { 51 | //whitelist the deployer so they can whitelist others 52 | constructor() public { 53 | whitelist[msg.sender] = true; 54 | } 55 | //to avoid replay 56 | mapping(address => uint) public nonce; 57 | // allow for third party metatx account to make transactions through this 58 | // contract like an identity but make sure the owner has whitelisted the tx 59 | mapping(address => bool) public whitelist; 60 | function updateWhitelist(address _account, bool _value) public returns(bool) { 61 | require(whitelist[msg.sender],"BouncerProxy::updateWhitelist Account Not Whitelisted"); 62 | whitelist[_account] = _value; 63 | UpdateWhitelist(_account,_value); 64 | return true; 65 | } 66 | event UpdateWhitelist(address _account, bool _value); 67 | // copied from https://github.com/uport-project/uport-identity/blob/develop/contracts/Proxy.sol 68 | function () public payable { emit Received(msg.sender, msg.value); } 69 | event Received (address indexed sender, uint value); 70 | 71 | function getHash(address signer, address destination, uint value, bytes data, address rewardToken, uint rewardAmount) public view returns(bytes32){ 72 | return keccak256(abi.encodePacked(address(this), signer, destination, value, data, rewardToken, rewardAmount, nonce[signer])); 73 | } 74 | 75 | 76 | // original forward function copied from https://github.com/uport-project/uport-identity/blob/develop/contracts/Proxy.sol 77 | function forward(bytes sig, address signer, address destination, uint value, bytes data, address rewardToken, uint rewardAmount) public { 78 | //the hash contains all of the information about the meta transaction to be called 79 | bytes32 _hash = getHash(signer, destination, value, data, rewardToken, rewardAmount); 80 | //increment the hash so this tx can't run again 81 | nonce[signer]++; 82 | //this makes sure signer signed correctly AND signer is a valid bouncer 83 | require(signerIsWhitelisted(_hash,sig),"BouncerProxy::forward Signer is not whitelisted"); 84 | //make sure the signer pays in whatever token (or ether) the sender and signer agreed to 85 | // or skip this if the sender is incentivized in other ways and there is no need for a token 86 | if(rewardAmount>0){ 87 | //Address 0 mean reward with ETH 88 | if(rewardToken==address(0)){ 89 | //REWARD ETHER 90 | require(msg.sender.call.value(rewardAmount).gas(36000)()); 91 | }else{ 92 | //REWARD TOKEN 93 | require((StandardToken(rewardToken)).transfer(msg.sender,rewardAmount)); 94 | } 95 | } 96 | //execute the transaction with all the given parameters 97 | require(executeCall(destination, value, data)); 98 | emit Forwarded(sig, signer, destination, value, data, rewardToken, rewardAmount, _hash); 99 | } 100 | // when some frontends see that a tx is made from a bouncerproxy, they may want to parse through these events to find out who the signer was etc 101 | event Forwarded (bytes sig, address signer, address destination, uint value, bytes data,address rewardToken, uint rewardAmount,bytes32 _hash); 102 | 103 | // copied from https://github.com/uport-project/uport-identity/blob/develop/contracts/Proxy.sol 104 | // which was copied from GnosisSafe 105 | // https://github.com/gnosis/gnosis-safe-contracts/blob/master/contracts/GnosisSafe.sol 106 | function executeCall(address to, uint256 value, bytes data) internal returns (bool success) { 107 | assembly { 108 | success := call(gas, to, value, add(data, 0x20), mload(data), 0, 0) 109 | } 110 | } 111 | 112 | //borrowed from OpenZeppelin's ESDA stuff: 113 | //https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/cryptography/ECDSA.sol 114 | function signerIsWhitelisted(bytes32 _hash, bytes _signature) internal view returns (bool){ 115 | bytes32 r; 116 | bytes32 s; 117 | uint8 v; 118 | // Check the signature length 119 | if (_signature.length != 65) { 120 | return false; 121 | } 122 | // Divide the signature in r, s and v variables 123 | // ecrecover takes the signature parameters, and the only way to get them 124 | // currently is to use assembly. 125 | // solium-disable-next-line security/no-inline-assembly 126 | assembly { 127 | r := mload(add(_signature, 32)) 128 | s := mload(add(_signature, 64)) 129 | v := byte(0, mload(add(_signature, 96))) 130 | } 131 | // Version of signature should be 27 or 28, but 0 and 1 are also possible versions 132 | if (v < 27) { 133 | v += 27; 134 | } 135 | // If the version is correct return the signer address 136 | if (v != 27 && v != 28) { 137 | return false; 138 | } else { 139 | // solium-disable-next-line arg-overflow 140 | return whitelist[ecrecover(keccak256( 141 | abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash) 142 | ), v, r, s)]; 143 | } 144 | } 145 | } 146 | 147 | contract StandardToken { 148 | function transfer(address _to,uint256 _value) public returns (bool) { } 149 | } 150 | -------------------------------------------------------------------------------- /BouncerProxy/arguments.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example of passing in a string to the constructor: 3 | module.exports = ["hello world"] 4 | */ 5 | 6 | module.exports = [] 7 | -------------------------------------------------------------------------------- /BouncerProxy/dependencies.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs'); 3 | module.exports = {} 4 | -------------------------------------------------------------------------------- /Example/Example.compiled: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | 5 | This is just a very simple example contract used to demonstrate the BouncerProxy making calls to it 6 | 7 | */ 8 | 9 | 10 | contract Example { 11 | 12 | constructor() public { } 13 | 14 | //it keeps a count to demonstrate stage changes 15 | uint public count = 0; 16 | 17 | //it can receive funds 18 | function () payable { emit Received(msg.sender, msg.value); } 19 | event Received (address indexed sender, uint value); 20 | 21 | //and it can add to a count 22 | function addAmount(uint256 amount) public returns (bool) { 23 | count = count + amount; 24 | return true; 25 | } 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /Example/Example.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | 5 | This is just a very simple example contract used to demonstrate the BouncerProxy making calls to it 6 | 7 | */ 8 | 9 | 10 | contract Example { 11 | 12 | constructor() public { } 13 | 14 | //it keeps a count to demonstrate stage changes 15 | uint public count = 0; 16 | 17 | //it can receive funds 18 | function () payable { emit Received(msg.sender, msg.value); } 19 | event Received (address indexed sender, uint value); 20 | 21 | //and it can add to a count 22 | function addAmount(uint256 amount) public returns (bool) { 23 | count = count + amount; 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/arguments.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example of passing in a string to the constructor: 3 | module.exports = ["hello world"] 4 | */ 5 | 6 | module.exports = [] 7 | -------------------------------------------------------------------------------- /Example/dependencies.js: -------------------------------------------------------------------------------- 1 | /* 2 | Needed to inherit Zeppelin's Ownable contract: 3 | 4 | const fs = require('fs'); 5 | module.exports = { 6 | 'openzeppelin-solidity/contracts/ownership/Ownable.sol': fs.readFileSync('openzeppelin-solidity/contracts/ownership/Ownable.sol', 'utf8') 7 | } 8 | */ 9 | module.exports = {} 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Austin Griffith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👮🏻🛰 bouncer proxy 2 | _Send Ether and interact with smart contracts from etherless accounts through a 'Bouncer Proxy' identity contract. Inspired by Bouncers, Proxy Identities, and Universal Logins._ 3 | 4 | [https://prox.metatx.io](https://prox.metatx.io) 5 | 6 | [Ethereum Meta Transactions](https://medium.com/@austin_48503/ethereum-meta-transactions-90ccf0859e84) 7 | 8 | ----- 9 | 10 | [![screencast.png](https://raw.githubusercontent.com/austintgriffith/bouncer-proxy/master/src/images/videopreview.jpg)](https://youtu.be/6r3SqCcEVU4) 11 | 12 | ----- 13 | 14 | ## Identity 15 | 16 | A [BouncerProxy](https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol) acts as your identity and allows multiple devices to be added as 'bouncers'. These 'bouncers' can make Ethereum transactions through your proxy without holding any Ether. That means no sending Ether to a device you might lose and no moving private keys or seed phrases around. 17 | 18 | You deploy your own [BouncerProxy](https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol) once, fund it with Ethereum and/or tokens from a cold account, and keep your private keys safe. 19 | 20 | ## Pay gas for your Dapp users' transactions 21 | 22 | To lower the barrier to entry you can proxy transactions from your users. To do this, you'll need to change your frontend slightly. Transactions will no longer go directly to the Ethereum network but to an off-chain relay that you control. 23 | 24 | Your users will craft and sign transactions in almost the exact same way, but then you will relay them to a [BouncerProxy](https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol). This proxy acts as a proxy identity and an authorization controller. Accounts you accept as 'bouncers' can transact through the proxy without spending any Ether. 25 | 26 | ## Incentivize 'Desktop Miners' 27 | 28 | Meta transactions can be submitted to your [BouncerProxy](https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol) by any account. You can choose to run your own relay or incentive others to do so by [rewarding Ether](https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol#L69) or [rewarding tokens](https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol#L73) to distributed relayers. 29 | 30 | ## Inspired by: 31 | 32 | @avsa - [https://www.youtube.com/watch?v=qF2lhJzngto](https://www.youtube.com/watch?v=qF2lhJzngto) 33 | 34 | @mattgcondon - [https://twitter.com/mattgcondon/status/1022287545139449856](https://twitter.com/mattgcondon/status/1022287545139449856) && [https://twitter.com/mattgcondon/status/1021984009428107264](https://twitter.com/mattgcondon/status/1021984009428107264) 35 | 36 | @owocki - [https://twitter.com/owocki/status/1021859962882908160](https://twitter.com/owocki/status/1021859962882908160) 37 | 38 | @danfinlay - [https://twitter.com/danfinlay/status/1022271384938983424](https://twitter.com/danfinlay/status/1022271384938983424) 39 | 40 | @PhABCD - [https://twitter.com/PhABCD/status/1021974772786319361](https://twitter.com/PhABCD/status/1021974772786319361) 41 | 42 | [gnosis-safe](https://github.com/gnosis/safe-contracts) 43 | 44 | [uport-identity](https://github.com/uport-project/uport-identity) 45 | -------------------------------------------------------------------------------- /SomeToken/SomeToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | 5 | This is just an example token to test out the incentives in a BouncerProxy 6 | 7 | */ 8 | 9 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; 10 | 11 | 12 | contract SomeToken is ERC20Mintable { 13 | 14 | string public name = "SomeToken"; 15 | string public symbol = "ST"; 16 | uint8 public decimals = 18; 17 | 18 | constructor() public { } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SomeToken/arguments.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example of passing in a string to the constructor: 3 | module.exports = ["hello world"] 4 | */ 5 | 6 | module.exports = [] 7 | -------------------------------------------------------------------------------- /SomeToken/dependencies.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | module.exports = { 3 | 'openzeppelin-solidity/contracts/math/SafeMath.sol': fs.readFileSync('openzeppelin-solidity/contracts/math/SafeMath.sol', 'utf8'), 4 | 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'utf8'), 5 | 'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/IERC20.sol', 'utf8'), 6 | 'openzeppelin-solidity/contracts/ownership/Ownable.sol': fs.readFileSync('openzeppelin-solidity/contracts/ownership/Ownable.sol', 'utf8'), 7 | 'openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol', 'utf8'), 8 | 'openzeppelin-solidity/contracts/access/roles/MinterRole.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/roles/MinterRole.sol', 'utf8'), 9 | 'openzeppelin-solidity/contracts/access/Roles.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/Roles.sol', 'utf8'), 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /attach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker exec -ti clevis bash 3 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const EventParser = require('./modules/eventParser.js'); 3 | const LiveParser = require('./modules/liveParser.js'); 4 | const express = require('express'); 5 | const helmet = require('helmet'); 6 | const app = express(); 7 | const fs = require('fs'); 8 | const Redis = require('ioredis'); 9 | const ContractLoader = require('./modules/contractLoader.js'); 10 | var bodyParser = require('body-parser') 11 | app.use(bodyParser.json()); 12 | app.use(bodyParser.urlencoded({extended: true})); 13 | app.use(helmet()); 14 | var cors = require('cors') 15 | app.use(cors()) 16 | let contracts; 17 | let tokens = []; 18 | var Web3 = require('web3'); 19 | var web3 = new Web3(); 20 | web3.setProvider(new web3.providers.HttpProvider('http://0.0.0.0:8545')); 21 | 22 | const DESKTOPMINERACCOUNT = 3 //index in geth 23 | 24 | let accounts 25 | web3.eth.getAccounts().then((_accounts)=>{ 26 | accounts=_accounts 27 | console.log("ACCOUNTS",accounts) 28 | }) 29 | 30 | const NETWORK = parseInt(fs.readFileSync("../deploy.network").toString().trim()) 31 | if(!NETWORK){ 32 | console.log("No deploy.network found exiting...") 33 | process.exit() 34 | } 35 | console.log("NETWORK:",NETWORK) 36 | 37 | let redisHost = 'localhost' 38 | let redisPort = 57300 39 | if(NETWORK>0&&NETWORK<9){ 40 | redisHost = 'cryptogsnew.048tmy.0001.use2.cache.amazonaws.com' 41 | redisPort = 6379 42 | } 43 | let redis = new Redis({ 44 | port: redisPort, 45 | host: redisHost, 46 | }) 47 | 48 | console.log("LOADING CONTRACTS") 49 | contracts = ContractLoader(["BouncerProxy","Example"],web3); 50 | 51 | 52 | 53 | //my local geth node takes a while to spin up so I don't want to start parsing until I'm getting real data 54 | function checkForGeth() { 55 | contracts["Example"].methods.count().call({}, function(error, result){ 56 | console.log("COUNT",error,result) 57 | if(error){ 58 | setTimeout(checkForGeth,15000); 59 | }else{ 60 | startParsers() 61 | } 62 | }); 63 | } 64 | checkForGeth() 65 | 66 | function startParsers(){ 67 | web3.eth.getBlockNumber().then((blockNumber)=>{ 68 | //parsers here 69 | }) 70 | } 71 | 72 | app.get('/', (req, res) => { 73 | res.setHeader('Access-Control-Allow-Origin', '*'); 74 | console.log("/") 75 | res.set('Content-Type', 'application/json'); 76 | res.end(JSON.stringify({hello:"world"})); 77 | 78 | }); 79 | 80 | app.get('/miner', (req, res) => { 81 | res.setHeader('Access-Control-Allow-Origin', '*'); 82 | console.log("/miner") 83 | res.set('Content-Type', 'application/json'); 84 | res.end(JSON.stringify({address:accounts[DESKTOPMINERACCOUNT]})); 85 | 86 | }); 87 | 88 | app.get('/sigs/:contract', (req, res) => { 89 | res.setHeader('Access-Control-Allow-Origin', '*'); 90 | console.log("/sigs/"+req.params.contract) 91 | let sigsKey = req.params.contract+"sigs" 92 | redis.get(sigsKey, function (err, result) { 93 | res.set('Content-Type', 'application/json'); 94 | res.end(result); 95 | }) 96 | 97 | }); 98 | 99 | app.get('/contracts', (req, res) => { 100 | res.setHeader('Access-Control-Allow-Origin', '*'); 101 | console.log("/contracts") 102 | let deployedContractsKey = "deployedcontracts"+NETWORK 103 | redis.get(deployedContractsKey, function (err, result) { 104 | res.set('Content-Type', 'application/json'); 105 | res.end(result); 106 | }) 107 | 108 | }); 109 | 110 | app.post('/sign', (req, res) => { 111 | res.setHeader('Access-Control-Allow-Origin', '*'); 112 | console.log("/sign",req.body) 113 | let account = web3.eth.accounts.recover(req.body.message,req.body.sig) 114 | console.log("RECOVERED:",account) 115 | if(account.toLowerCase()==req.body.account.toLowerCase()){ 116 | console.log("Correct sig... log them into the contract...") 117 | let sigsKey = req.body.address+"sigs" 118 | redis.get(sigsKey, function (err, result) { 119 | let sigs 120 | try{ 121 | sigs = JSON.parse(result) 122 | }catch(e){sigs = []} 123 | if(!sigs) sigs = [] 124 | console.log("current sigs:",sigs) 125 | if(sigs.indexOf(req.body.account.toLowerCase())<0){ 126 | sigs.push(req.body.account.toLowerCase()) 127 | console.log("saving sigs:",sigs) 128 | redis.set(sigsKey,JSON.stringify(sigs),'EX', 60 * 60 * 24 * 7); 129 | } 130 | }); 131 | } 132 | res.set('Content-Type', 'application/json'); 133 | res.end(JSON.stringify({hello:"world"})); 134 | }); 135 | 136 | app.post('/deploy', (req, res) => { 137 | res.setHeader('Access-Control-Allow-Origin', '*'); 138 | console.log("/deploy",req.body) 139 | let contractAddress = req.body.contractAddress 140 | let deployedContractsKey = "deployedcontracts"+NETWORK 141 | redis.get(deployedContractsKey, function (err, result) { 142 | let contracts 143 | try{ 144 | contracts = JSON.parse(result) 145 | }catch(e){contracts = []} 146 | if(!contracts) contracts = [] 147 | console.log("current contracts:",contracts) 148 | if(contracts.indexOf(contractAddress)<0){ 149 | contracts.push(contractAddress) 150 | } 151 | console.log("saving contracts:",contracts) 152 | redis.set(deployedContractsKey,JSON.stringify(contracts),'EX', 60 * 60 * 24 * 7); 153 | res.set('Content-Type', 'application/json'); 154 | res.end(JSON.stringify({contract:contractAddress})); 155 | }); 156 | }) 157 | 158 | let txsKey = "txs" 159 | 160 | app.get('/txs/:account', (req, res) => { 161 | res.setHeader('Access-Control-Allow-Origin', '*'); 162 | console.log("/txs/"+req.params.account) 163 | let thisTxsKey = txsKey+req.params.account.toLowerCase() 164 | console.log("Getting Transactions for ",thisTxsKey) 165 | redis.get(thisTxsKey, function (err, result) { 166 | res.set('Content-Type', 'application/json'); 167 | res.end(result); 168 | }) 169 | }); 170 | 171 | let txHashkey = "tx" 172 | app.get('/tx/:hash', (req, res) => { 173 | res.setHeader('Access-Control-Allow-Origin', '*'); 174 | console.log("/tx/"+req.params.hash) 175 | let thisTxsKey = txHashkey+req.params.hash.toLowerCase() 176 | console.log("Getting Transaction with hash ",thisTxsKey) 177 | redis.get(thisTxsKey, function (err, result) { 178 | res.set('Content-Type', 'application/json'); 179 | res.end(result); 180 | }) 181 | }); 182 | 183 | app.post('/tx', async (req, res) => { 184 | res.setHeader('Access-Control-Allow-Origin', '*'); 185 | console.log("/tx",req.body) 186 | let account = web3.eth.accounts.recover(req.body.message,req.body.sig) 187 | console.log("RECOVERED:",account) 188 | if(account.toLowerCase()==req.body.parts[1].toLowerCase()){ 189 | console.log("Correct sig... relay transaction to contract... might want more filtering here, but just blindly do it for now") 190 | 191 | //console.log(contracts.BouncerProxy) 192 | let contract = new web3.eth.Contract(contracts.BouncerProxy._jsonInterface,req.body.parts[0]) 193 | console.log("Forwarding tx to ",contract._address," with local account ",accounts[3]) 194 | 195 | let txparams = { 196 | from: accounts[DESKTOPMINERACCOUNT], 197 | gas: req.body.gas, 198 | gasPrice:Math.round(4 * 1000000000) 199 | } 200 | //first get the hash to see if there is already a tx in motion 201 | let hash = await contract.methods.getHash(req.body.parts[1],req.body.parts[2],req.body.parts[3],req.body.parts[4],req.body.parts[5],req.body.parts[6]).call() 202 | console.log("HASH:",hash) 203 | 204 | let thisTxHashkey = txHashkey+hash.toLowerCase() 205 | 206 | console.log("Checking centralized db for collision before mining....") 207 | let thisTxsKey = txHashkey+hash.toLowerCase() 208 | console.log("Getting Transaction with hash ",thisTxsKey) 209 | redis.get(thisTxsKey, function (err, result) { 210 | if(result){ 211 | console.log("FOUND EXISTING TX",result) 212 | }else{ 213 | console.log("NO EXISTING TX, DOING TX") 214 | //const result = await clevis("contract","forward","BouncerProxy",accountIndexSender,sig,accounts[accountIndexSigner],localContractAddress("Example"),"0",data,rewardAddress,reqardAmount) 215 | console.log("TX",req.body.sig,req.body.parts[1],req.body.parts[2],req.body.parts[3],req.body.parts[4],req.body.parts[5],req.body.parts[6]) 216 | console.log("PARAMS",txparams) 217 | contract.methods.forward(req.body.sig,req.body.parts[1],req.body.parts[2],req.body.parts[3],req.body.parts[4],req.body.parts[5],req.body.parts[6]).send( 218 | txparams ,(error, transactionHash)=>{ 219 | console.log("TX CALLBACK",error,transactionHash) 220 | //currentTransactions.push({hash:transactionHash,time:Date.now(),addedFromCallback:1}) 221 | let thisTxsKey = txsKey+req.body.parts[1].toLowerCase() 222 | redis.get(thisTxsKey, function (err, result) { 223 | let transactions 224 | try{ 225 | transactions = JSON.parse(result) 226 | }catch(e){transactions = []} 227 | if(!transactions) transactions = [] 228 | console.log("current transactions:",transactions) 229 | if(transactions.indexOf(transactions)<0){ 230 | transactions.push({hash:transactionHash,time:Date.now(),metatx:true,miner:accounts[DESKTOPMINERACCOUNT]}) 231 | } 232 | console.log("saving transactions for "+txsKey+req.body.parts[1]+":",transactions) 233 | redis.set(thisTxsKey,JSON.stringify(transactions),'EX', 60 * 60 * 24 * 7); 234 | 235 | 236 | //write tx hash also 237 | redis.set(thisTxHashkey,transactionHash,'EX', 60 * 60 * 24 * 7); 238 | }) 239 | } 240 | ) 241 | .on('error',(err,receiptMaybe)=>{ 242 | console.log("TX ERROR",err,receiptMaybe) 243 | }) 244 | .on('transactionHash',(transactionHash)=>{ 245 | console.log("TX HASH",transactionHash) 246 | }) 247 | .on('receipt',(receipt)=>{ 248 | console.log("TX RECEIPT",receipt) 249 | }) 250 | /*.on('confirmation', (confirmations,receipt)=>{ 251 | console.log("TX CONFIRM",confirmations,receipt) 252 | })*/ 253 | .then((receipt)=>{ 254 | console.log("TX THEN",receipt) 255 | }) 256 | } 257 | }) 258 | } 259 | res.set('Content-Type', 'application/json'); 260 | res.end(JSON.stringify({hello:"world"})); 261 | }); 262 | 263 | app.listen(10001); 264 | console.log(`http listening on 10001`); 265 | -------------------------------------------------------------------------------- /backend/modules/contractLoader.js: -------------------------------------------------------------------------------- 1 | module.exports = function(contractList,web3){ 2 | let contracts = [] 3 | for(let c in contractList){ 4 | try{ 5 | let abi = require("../../src/contracts/"+contractList[c]+".abi.js") 6 | let address = require("../../src/contracts/"+contractList[c]+".address.js") 7 | console.log(contractList[c],address,abi.length) 8 | contracts[contractList[c]] = new web3.eth.Contract(abi,address) 9 | console.log("contract") 10 | contracts[contractList[c]].blockNumber = require("../../src/contracts/"+contractList[c]+".blocknumber.js") 11 | console.log("@ Block",contracts[contractList[c]].blockNumber) 12 | }catch(e){console.log(e)} 13 | } 14 | return contracts 15 | } 16 | -------------------------------------------------------------------------------- /backend/modules/eventParser.js: -------------------------------------------------------------------------------- 1 | const EVENTLOADCHUNK = 250000; 2 | let LASTBLOCK; 3 | let ENDBLOCK; 4 | const DEBUG = false; 5 | module.exports = function(contract,eventName,endingBlock,startingBlock,updateFn,filter){ 6 | LASTBLOCK=parseInt(startingBlock) 7 | ENDBLOCK=parseInt(endingBlock) 8 | loadDownTheChain(contract,eventName,updateFn,filter) 9 | } 10 | let loadDownTheChain = async (contract,eventName,updateFn,filter)=>{ 11 | while(LASTBLOCK>=ENDBLOCK){ 12 | let nextLast = LASTBLOCK-EVENTLOADCHUNK 13 | if(nextLast{ 19 | from = Math.max(0,from) 20 | if(DEBUG) console.log("EVENT:",eventName,"FROM",from,"to",to,contract) 21 | let events 22 | try{ 23 | if(filter){ 24 | events = await contract.getPastEvents(eventName, { 25 | filter: filter, 26 | fromBlock: from, 27 | toBlock: to 28 | }); 29 | }else{ 30 | events = await contract.getPastEvents(eventName, { 31 | fromBlock: from, 32 | toBlock: to 33 | }); 34 | } 35 | for(let e in events){ 36 | let thisEvent = events[e].returnValues 37 | thisEvent.blockNumber = events[e].blockNumber 38 | updateFn(thisEvent); 39 | } 40 | }catch(e){console.log(e)} 41 | return true; 42 | } 43 | -------------------------------------------------------------------------------- /backend/modules/liveParser.js: -------------------------------------------------------------------------------- 1 | const LOOKBACK = 16; 2 | module.exports = async function(contract,eventName,currentBlock,updateFn,filter){ 3 | let from = parseInt(currentBlock)-LOOKBACK 4 | let to = 'latest' 5 | let events 6 | if(filter){ 7 | events = await contract.getPastEvents(eventName, { 8 | filter: filter, 9 | fromBlock: from, 10 | toBlock: to 11 | }); 12 | }else{ 13 | events = await contract.getPastEvents(eventName, { 14 | fromBlock: from, 15 | toBlock: to 16 | }); 17 | } 18 | for(let e in events){ 19 | let thisEvent = events[e].returnValues; 20 | thisEvent.blockNumber = events[e].blockNumber 21 | updateFn(thisEvent); 22 | } 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /backend/redis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker rm -f bouncer-proxy-redis 3 | docker run --name bouncer-proxy-redis -v ${PWD}/redisdata:/data -p 57300:6379 -d redis redis-server --appendonly yes 4 | -------------------------------------------------------------------------------- /backend/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf redisdata/ ; ./redis.sh ; nodemon index.js 3 | -------------------------------------------------------------------------------- /backend/simple.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const express = require('express'); 3 | const helmet = require('helmet'); 4 | const app = express(); 5 | const fs = require('fs'); 6 | const ContractLoader = require('./modules/contractLoader.js'); 7 | const Room = require('ipfs-pubsub-room') 8 | const IPFS = require('ipfs') 9 | const ipfs = new IPFS({ 10 | repo: './ipfs', 11 | EXPERIMENTAL: { 12 | pubsub: true 13 | }, 14 | config: { 15 | Addresses: { 16 | Swarm: [ 17 | '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star' 18 | ] 19 | } 20 | } 21 | }) 22 | let contracts; 23 | let tokens = []; 24 | var Web3 = require('web3'); 25 | var web3 = new Web3(); 26 | web3.setProvider(new web3.providers.HttpProvider('http://0.0.0.0:8545')); 27 | 28 | const DESKTOPMINERACCOUNT = 3 //index in geth 29 | 30 | const IPFSROOMNAME = "bouncer-proxy" 31 | 32 | let accounts 33 | web3.eth.getAccounts().then((_accounts)=>{ 34 | accounts=_accounts 35 | console.log("ACCOUNTS",accounts) 36 | }) 37 | 38 | console.log("LOADING CONTRACTS") 39 | contracts = ContractLoader(["BouncerProxy","Example"],web3); 40 | 41 | //my local geth node takes a while to spin up so I don't want to start parsing until I'm getting real data 42 | function checkForGeth() { 43 | contracts["Example"].methods.count().call({}, function(error, result){ 44 | console.log("COUNT",error,result) 45 | if(error){ 46 | setTimeout(checkForGeth,60000); 47 | }else{ 48 | startParsers() 49 | } 50 | }); 51 | } 52 | checkForGeth() 53 | 54 | function startParsers(){ 55 | web3.eth.getBlockNumber().then((blockNumber)=>{ 56 | //parsers here 57 | }) 58 | } 59 | 60 | ipfs.on('ready', () => { 61 | const room = Room(ipfs,IPFSROOMNAME) 62 | 63 | const ipfsMiners = Room(ipfs,IPFSROOMNAME+"Miners") 64 | setInterval(()=>{ 65 | console.log("Checking in...") 66 | ipfsMiners.broadcast(JSON.stringify({address:accounts[DESKTOPMINERACCOUNT]})) 67 | },15000) 68 | 69 | ipfsMiners.on('peer joined ipfsMiners', (peer) => { 70 | console.log('Peer joined the room', peer) 71 | ipfsMiners.sendTo(peer, JSON.stringify({address:accounts[DESKTOPMINERACCOUNT]})) 72 | }) 73 | 74 | room.on('peer left', (peer) => { 75 | console.log('Peer left...', peer) 76 | }) 77 | 78 | // now started to listen to room 79 | room.on('subscribed', () => { 80 | console.log('Now connected!') 81 | }) 82 | 83 | room.on('message', async (message) => { 84 | 85 | console.log("message:",message) 86 | let metaTxData = JSON.parse(message.data) 87 | console.log(metaTxData) 88 | console.log("/tx",metaTxData) 89 | let account = web3.eth.accounts.recover(metaTxData.message,metaTxData.sig) 90 | console.log("RECOVERED:",account) 91 | if(account.toLowerCase()==metaTxData.parts[1].toLowerCase()){ 92 | console.log("Correct sig... relay transaction to contract... might want more filtering here, but just blindly do it for now") 93 | 94 | //console.log(contracts.BouncerProxy) 95 | let contract = new web3.eth.Contract(contracts.BouncerProxy._jsonInterface,metaTxData.parts[0]) 96 | console.log("Forwarding tx to ",contract._address," with local account ",accounts[3]) 97 | 98 | let txparams = { 99 | from: accounts[DESKTOPMINERACCOUNT], 100 | gas: metaTxData.gas, 101 | gasPrice:Math.round(4 * 1000000000) 102 | } 103 | //first get the hash to see if there is already a tx in motion 104 | let hash = await contract.methods.getHash(metaTxData.parts[1],metaTxData.parts[2],metaTxData.parts[3],metaTxData.parts[4],metaTxData.parts[5],metaTxData.parts[6]).call() 105 | console.log("HASH:",hash) 106 | 107 | 108 | console.log("NO EXISTING TX, DOING TX") 109 | //const result = await clevis("contract","forward","BouncerProxy",accountIndexSender,sig,accounts[accountIndexSigner],localContractAddress("Example"),"0",data,rewardAddress,reqardAmount) 110 | console.log("TX",metaTxData.sig,metaTxData.parts[1],metaTxData.parts[2],metaTxData.parts[3],metaTxData.parts[4],metaTxData.parts[5],metaTxData.parts[6]) 111 | console.log("PARAMS",txparams) 112 | contract.methods.forward(metaTxData.sig,metaTxData.parts[1],metaTxData.parts[2],metaTxData.parts[3],metaTxData.parts[4],metaTxData.parts[5],metaTxData.parts[6]).send( 113 | txparams ,(error, transactionHash)=>{ 114 | console.log("TX CALLBACK",error,transactionHash) 115 | //currentTransactions.push({hash:transactionHash,time:Date.now(),addedFromCallback:1}) 116 | //let thisTxsKey = txsKey+metaTxData.parts[1].toLowerCase() 117 | /*redis.get(thisTxsKey, function (err, result) { 118 | let transactions 119 | try{ 120 | transactions = JSON.parse(result) 121 | }catch(e){transactions = []} 122 | if(!transactions) transactions = [] 123 | console.log("current transactions:",transactions) 124 | if(transactions.indexOf(transactions)<0){ 125 | transactions.push({hash:transactionHash,time:Date.now(),metatx:true,miner:accounts[DESKTOPMINERACCOUNT]}) 126 | } 127 | console.log("saving transactions for "+txsKey+metaTxData.parts[1]+":",transactions) 128 | redis.set(thisTxsKey,JSON.stringify(transactions),'EX', 60 * 60 * 24 * 7); 129 | 130 | 131 | //write tx hash also 132 | redis.set(thisTxHashkey,transactionHash,'EX', 60 * 60 * 24 * 7); 133 | })*/ 134 | } 135 | ) 136 | .on('error',(err,receiptMaybe)=>{ 137 | console.log("TX ERROR",err,receiptMaybe) 138 | }) 139 | .on('transactionHash',(transactionHash)=>{ 140 | console.log("TX HASH",transactionHash) 141 | }) 142 | .on('receipt',(receipt)=>{ 143 | console.log("TX RECEIPT",receipt) 144 | }) 145 | .then((receipt)=>{ 146 | console.log("TX THEN",receipt) 147 | }) 148 | 149 | } 150 | }) 151 | }) 152 | 153 | 154 | /* 155 | 156 | 157 | app.post('/tx', async (req, res) => { 158 | res.setHeader('Access-Control-Allow-Origin', '*'); 159 | 160 | res.set('Content-Type', 'application/json'); 161 | res.end(JSON.stringify({hello:"world"})); 162 | }); 163 | 164 | app.listen(10001); 165 | console.log(`http listening on 10001`); 166 | */ 167 | -------------------------------------------------------------------------------- /clevis.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "http://localhost:8545", 3 | "gasprice": 8.6, 4 | "ethprice": 228.254491794, 5 | "deploygas": 5500000, 6 | "xfergas": 1300000, 7 | "DEBUG": true 8 | } 9 | -------------------------------------------------------------------------------- /contracts.clevis: -------------------------------------------------------------------------------- 1 | BouncerProxy 2 | Example 3 | SomeToken -------------------------------------------------------------------------------- /dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | osascript -e 'tell application "iTerm" 3 | activate 4 | create window with profile "Default" 5 | tell the current window 6 | tell the current session 7 | delay 1 8 | write text "npm start" 9 | delay 1 10 | end tell 11 | end tell 12 | end tell' 13 | osascript -e 'tell application "iTerm" 14 | activate 15 | create window with profile "Default" 16 | tell the current window 17 | tell the current session 18 | delay 1 19 | write text "ganache-cli" 20 | delay 1 21 | end tell 22 | end tell 23 | end tell' 24 | osascript -e 'tell application "iTerm" 25 | activate 26 | create window with profile "Default" 27 | tell the current window 28 | tell the current session 29 | delay 1 30 | write text "cd backend;sleep 15;./redis.sh;nodemon index.js" 31 | delay 1 32 | end tell 33 | end tell 34 | end tell' 35 | #tell application "Chrome" 36 | # open location "http://localhost:8000" 37 | #end tell' 38 | atom . -n 39 | clevis test full 40 | ## you also need to pull in repos like zep 41 | ## 42 | ## I also had to: 43 | ## sudo npm install -g scrypt 44 | ## install "scrypt" manually ("sudo npm install -g scrypt") 45 | ## go to "ethereum-testrpc module folder"/node_modules/scrypt 46 | ## mv build buildold 47 | ## make a symbolic link to scrypt build folder e.g. "ln -s /usr/local/lib/node_modules/scrypt/build" 48 | ## then I got in there 49 | ## and did cp scrypt.node scrypt 50 | ##https://github.com/trufflesuite/ganache-cli/issues/134 51 | 52 | 53 | ## I went to /Users/austingriffith/cryptogs/gatsby-site/node_modules/scrypt 54 | ## mv build buildold 55 | ## ln -s /usr/local/lib/node_modules/scrypt/build 56 | ## 57 | ## had to move my ~/.node-gyp folder and regen 58 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update 4 | RUN apt-get dist-upgrade -y 5 | RUN apt-get upgrade -y 6 | RUN apt-get install build-essential python htop -y 7 | RUN apt-get install curl -y 8 | RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - 9 | RUN apt-get install -y nodejs 10 | RUN apt-get install software-properties-common -y 11 | RUN add-apt-repository -y ppa:ethereum/ethereum 12 | RUN apt-get update && apt-get install ethereum -y 13 | 14 | RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/* 15 | 16 | RUN apt-get update 17 | RUN npm i npm@latest -g 18 | RUN npm config set user 0 19 | RUN npm config set unsafe-perm true 20 | RUN npm install -g ganache-cli 21 | RUN npm install -g npx 22 | RUN apt-get install git-core -y 23 | 24 | 25 | ADD package.json package.json 26 | RUN npm i 27 | 28 | ADD bootstrap.sh /bootstrap.sh 29 | RUN chmod +x /bootstrap.sh 30 | 31 | 32 | CMD ../bootstrap.sh 33 | -------------------------------------------------------------------------------- /docker/attach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #THIS ONLY WORKS ON THE MAINNET NEED TO USE DIFFERENT FOLDER FOR TESTNETS 4 | docker exec -ti metatxrelay bash ic "sudo geth attach --datadir '/root/.ethereum'" 5 | -------------------------------------------------------------------------------- /docker/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z "$network" ]; then 3 | network="local" 4 | fi 5 | 6 | echo "Launching Meta Transaction ⛏️ miner with network [ $network ]..." 7 | 8 | if [ "$network" = "rinkeby" ]; then 9 | echo "Launching Rinkeby Geth..." 10 | /usr/bin/geth --rinkeby --syncmode "light" --cache 512 --maxpeers 25 --datadir ".geth-rinkeby" --rpc --rpcaddr 0.0.0.0 --rpcapi="db,eth,net,web3,personal" > geth.log 2>&1 & 11 | elif [ "$network" = "ropsten" ]; then 12 | echo "Launching Ropsten Geth..." 13 | /usr/bin/geth --testnet --syncmode "light" --cache 512 --maxpeers 25 --datadir ".geth-ropsten" --rpc --rpcaddr 0.0.0.0 --rpcapi="db,eth,net,web3,personal" > geth.log 2>&1 & 14 | else 15 | echo "Launching Mainnet Geth..." 16 | /usr/bin/geth --syncmode "light" --cache 512 --maxpeers 25 --datadir ".geth" --rpc --rpcaddr 0.0.0.0 --rpcapi="db,eth,net,web3,personal" > geth.log 2>&1 & 17 | 18 | fi 19 | 20 | ln -s /node_modules /backend/node_modules 21 | 22 | #mount --bind /node_modules /backend/node_modules 23 | #fire up the backend here 24 | 25 | bash 26 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FILE="package.json" 3 | if [ -f $FILE ]; 4 | then 5 | echo "File $FILE exists." 6 | else 7 | echo "File $FILE does not exist, copying..." 8 | cp ../$FILE $FILE 9 | fi 10 | #copy in package json if it is different 11 | cmp -s package.json ../package.json > /dev/null 12 | if [ $? -eq 1 ]; then 13 | echo "package.json has updated... copying..."; 14 | cp ../package.json . 15 | else 16 | echo "package.json is unchanged."; 17 | fi 18 | docker build -t metatxrelay . 19 | -------------------------------------------------------------------------------- /docker/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker tag metatxrelay austingriffith/metatxrelay:$1 3 | docker tag metatxrelay austingriffith/metatxrelay:latest 4 | docker push austingriffith/metatxrelay 5 | -------------------------------------------------------------------------------- /docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bouncer-proxy", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws": "0.0.3-2", 7 | "clevis": "0.0.77", 8 | "cors": "^2.8.4", 9 | "dapparatus": "^1.0.22", 10 | "express": "^4.16.3", 11 | "helmet": "^3.13.0", 12 | "ioredis": "^3.2.2", 13 | "ipfs": "^0.32.3", 14 | "ipfs-pubsub-room": "^1.4.0", 15 | "mocha": "^5.2.0", 16 | "nodemon": "^1.18.4", 17 | "qrcode.react": "^0.8.0", 18 | "react": "^16.4.1", 19 | "react-blockies": "^1.3.0", 20 | "react-dom": "^16.4.1", 21 | "react-scripts": "1.1.4", 22 | "react-stack-grid": "^0.7.1", 23 | "s3": "^4.4.0", 24 | "web3": "^1.0.0-beta.35", 25 | "web3-utils": "^1.0.0-beta.35" 26 | }, 27 | "scripts": { 28 | "start": "PORT=8000 react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test --env=jsdom", 31 | "eject": "react-scripts eject" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker rm -f metatxrelay 3 | ## ---- No Network means use local ganache-cli 4 | docker run -ti --rm --name metatxrelay -p 3000:3000 -p 8545:8545 -v ${PWD}/../backend:/backend -v ${PWD}/../src:/src metatxrelay 5 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | 2 | ## whats next 3 | 4 | would love feedback from the community - this doesn't conform to the standards and it will need to 5 | 6 | more feedback from the 'desktop miners' about what txs in progress and what errors they have 7 | 8 | ENS on everything just like in the universal login demo 9 | - instead of passing around accounts it should be yourname.metatxr.eth 10 | 11 | this still requires frontends to change, I would like to hijack the .send function in web3 12 | then detect if they are logged into a metatx account or not and send the given tx as meta 13 | OR, work totally normal 14 | 15 | find a good method of sharing the open metatxs peer or peer so other desktop miners can participate 16 | 17 | clean up auth or maybe even create an allowance system 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bouncer-proxy", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws": "0.0.3-2", 7 | "clevis": "0.0.77", 8 | "cors": "^2.8.4", 9 | "dapparatus": "^1.0.22", 10 | "express": "^4.16.3", 11 | "helmet": "^3.13.0", 12 | "ioredis": "^3.2.2", 13 | "ipfs": "^0.32.3", 14 | "ipfs-pubsub-room": "^1.4.0", 15 | "mocha": "^5.2.0", 16 | "nodemon": "^1.18.4", 17 | "qrcode.react": "^0.8.0", 18 | "react": "^16.4.1", 19 | "react-blockies": "^1.3.0", 20 | "react-dom": "^16.4.1", 21 | "react-scripts": "1.1.4", 22 | "react-stack-grid": "^0.7.1", 23 | "s3": "^4.4.0", 24 | "web3": "^1.0.0-beta.35", 25 | "web3-utils": "^1.0.0-beta.35" 26 | }, 27 | "scripts": { 28 | "start": "PORT=8000 react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test --env=jsdom", 31 | "eject": "react-scripts eject" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austintgriffith/bouncer-proxy/7bbbb616f5d46094fa5fbf16c7cdb77d03b12bb8/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | metatx.io 10 | 11 | 12 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -ti --rm --name clevis -p 3000:3000 -p 8545:8545 -v ${PWD}:/dapp austingriffith/clevis 3 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family: sans-serif; 4 | color:#888888; 5 | font-size:28px; 6 | font-weight:bold; 7 | background-color: #313131; 8 | } 9 | a { 10 | color:#aaaaaa; 11 | text-decoration: none; 12 | } 13 | a:visited { 14 | color:#999999; 15 | text-decoration: none; 16 | } 17 | 18 | .col { 19 | text-align: center; 20 | padding: 10px; 21 | height: 100%; 22 | } 23 | 24 | 25 | .button{ 26 | padding:10px; 27 | margin:10px; 28 | border:1px solid #eeeeee; 29 | background-color:#e6e6e6; 30 | width:200px; 31 | text-align:center; 32 | cursor:pointer; 33 | } 34 | 35 | .titleCenter { 36 | position: fixed; 37 | top: 50%; 38 | left: 50%; 39 | -webkit-transform: translate(-50%, -50%); 40 | transform: translate(-50%, -50%); 41 | } 42 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Metamask, Gas, ContractLoader, Transactions, Events, Scaler, Blockie, Address, Button } from "dapparatus" 3 | import Web3 from 'web3'; 4 | import './App.css'; 5 | import Owner from "./components/owner.js" 6 | import AllBouncers from "./components/allBouncers.js" 7 | import Bouncer from "./components/bouncer.js" 8 | import Backend from "./components/backend.js" 9 | import Miner from "./components/miner.js" 10 | import QRCode from 'qrcode.react'; 11 | import axios from 'axios'; 12 | 13 | const Room = require('ipfs-pubsub-room') 14 | const IPFS = require('ipfs') 15 | const ipfs = new IPFS({ 16 | repo: './ipfs', 17 | EXPERIMENTAL: { 18 | pubsub: true 19 | }, 20 | config: { 21 | Addresses: { 22 | Swarm: [ 23 | '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star' 24 | ] 25 | } 26 | } 27 | }) 28 | const IPFSROOMNAME = "bouncer-proxy" 29 | 30 | let backendUrl = "http://localhost:10001/" 31 | console.log("window.location:",window.location) 32 | if(window.location.href.indexOf("metatx.io")>=0) 33 | { 34 | backendUrl = "https://backend.metatx.io/" 35 | } 36 | 37 | class App extends Component { 38 | constructor(props) { 39 | super(props); 40 | this.state = { 41 | web3: false, 42 | account: false, 43 | gwei: 4, 44 | address: window.location.pathname.replace("/",""), 45 | contract: false, 46 | owner: "", 47 | bouncer: "", 48 | ipfs: Room(ipfs,IPFSROOMNAME), 49 | ipfsSigs: Room(ipfs,IPFSROOMNAME+"Sigs"), 50 | ipfsMiners: Room(ipfs,IPFSROOMNAME+"Miners") 51 | } 52 | } 53 | deployBouncerProxy() { 54 | let {web3,tx,contracts} = this.state 55 | console.log("Deploying bouncer...") 56 | let code = require("./contracts/BouncerProxy.bytecode.js") 57 | tx(contracts.BouncerProxy._contract.deploy({data:code}),1220000,(receipt)=>{ 58 | console.log("~~~~~~ DEPLOY FROM DAPPARATUS:",receipt) 59 | if(receipt.contractAddress){ 60 | axios.post(backendUrl+'deploy', receipt, { 61 | headers: { 62 | 'Content-Type': 'application/json', 63 | } 64 | }).then((response)=>{ 65 | console.log("CACHE RESULT",response) 66 | window.location = "/"+receipt.contractAddress 67 | }) 68 | .catch((error)=>{ 69 | console.log(error); 70 | window.location = "/"+receipt.contractAddress 71 | }) 72 | } 73 | }) 74 | 75 | } 76 | updateBouncer(value){ 77 | console.log("UPDATE BOUNCER",value) 78 | this.setState({bouncer:value}) 79 | } 80 | render() { 81 | let {web3,account,contracts,tx,gwei,block,avgBlockTime,etherscan} = this.state 82 | 83 | let metamask = ( 84 | { 87 | console.log("metamask state update:",state) 88 | if(state.web3Provider) { 89 | state.web3 = new Web3(state.web3Provider) 90 | this.setState(state) 91 | } 92 | }} 93 | /> 94 | ) 95 | 96 | let connectedDisplay = "" 97 | let events = "" 98 | if(web3){ 99 | connectedDisplay = ( 100 |
101 | {return require(`${__dirname}/${path}`)}} 105 | onReady={(contracts,customLoader)=>{ 106 | console.log("contracts loaded",contracts) 107 | this.setState({contracts:contracts},async ()=>{ 108 | if(this.state.address){ 109 | console.log("Loading dyamic contract "+this.state.address) 110 | let dynamicContract = customLoader("BouncerProxy",this.state.address)//new this.state.web3.eth.Contract(require("./contracts/BouncerProxy.abi.js"),this.state.address) 111 | console.log("Checking to see if "+this.state.account+" is whitelisted in contract",dynamicContract) 112 | let whitelisted = await dynamicContract.whitelist(this.state.account).call() 113 | console.log("whitelisted:",whitelisted) 114 | this.setState({contract:dynamicContract,whitelisted:whitelisted}) 115 | } 116 | }) 117 | }} 118 | /> 119 | { 128 | console.log("Transactions component is ready:",state) 129 | this.setState(state) 130 | }} 131 | onReceipt={(transaction,receipt)=>{ 132 | // this is one way to get the deployed contract address, but instead I'll switch 133 | // to a more straight forward callback system above 134 | console.log("Transaction Receipt",transaction,receipt) 135 | /*if(receipt.contractAddress){ 136 | window.location = "/"+receipt.contractAddress 137 | }*/ 138 | }} 139 | /> 140 | { 142 | console.log("Gas price update:",state) 143 | this.setState(state,()=>{ 144 | console.log("GWEI set:",this.state) 145 | }) 146 | }} 147 | /> 148 |
149 | ) 150 | } 151 | 152 | let mainTitle = "" 153 | let contractDisplay = "" 154 | let qr = "" 155 | let backend = "" 156 | 157 | if(web3 && contracts){ 158 | if(!this.state.address){ 159 | mainTitle = ( 160 |
161 | 162 |
163 | metatx.io 164 |
165 |
166 | exploring etherless meta transactions and universal logins in ethereum 167 |
168 |
169 | 174 | 177 |
178 | 179 | 180 |
181 | 184 |
185 | 186 |
187 | 188 | 189 |
190 | 191 | ) 192 | 193 | }else if(this.state.contract){ 194 | 195 | qr = ( 196 |
197 | 198 | 199 | 200 |
201 | ) 202 | 203 | backend = ( 204 | 209 | ) 210 | 211 | let userDisplay = "" 212 | if(this.state.whitelisted){ 213 | 214 | userDisplay = ( 215 |
216 | { 219 | console.log("bouncerUpdate",bouncerUpdate) 220 | this.setState(bouncerUpdate) 221 | }} 222 | updateBouncer={this.updateBouncer.bind(this)} 223 | /> 224 |
225 | 229 |
230 |
231 | ) 232 | }else{ 233 | userDisplay = ( 234 |
235 | 239 |
240 | ) 241 | } 242 | 243 | let whitelisted = "" 244 | console.log("WHITELISTED",this.state.whitelisted) 245 | if(this.state.whitelisted){ 246 | whitelisted = "(You are whitelisted to transact through this contract)" 247 | } 248 | 249 | contractDisplay = ( 250 |
251 | 252 | 253 |

metatx.io

254 |
255 |
259 | {whitelisted} 260 |
261 |
262 | {userDisplay} 263 |
264 | ) 265 | }else{ 266 | contractDisplay = ( 267 |
268 | Connecting to {this.state.address} 269 |
270 | ) 271 | } 272 | }else{ 273 | contractDisplay = ( 274 |
275 |
276 | 277 |
278 | metatx.io 279 |
280 |
281 | please unlock metamask or mobile web3 provider 282 |
283 |
284 | 289 | 294 |
295 |
296 |
297 |
298 | ) 299 | } 300 | 301 | return ( 302 |
303 | {metamask} 304 | {connectedDisplay} 305 | {events} 306 | {mainTitle} 307 | {contractDisplay} 308 | {qr} 309 | {backend} 310 |
311 | ); 312 | } 313 | } 314 | 315 | export default App; 316 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/allBouncers.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Scaler, Blockie } from "dapparatus" 3 | import axios from 'axios'; 4 | import StackGrid from "react-stack-grid"; 5 | 6 | let pollInterval 7 | let pollTime = 1501 8 | 9 | class allBouncers extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | contracts: [] 14 | } 15 | } 16 | componentDidMount(){ 17 | pollInterval = setInterval(this.load.bind(this),pollTime) 18 | this.load() 19 | } 20 | componentWillUnmount(){ 21 | clearInterval(pollInterval) 22 | } 23 | async load(){ 24 | let {contracts} = this.props 25 | axios.get(this.props.backendUrl+"contracts") 26 | .then((response)=>{ 27 | //console.log("CONTRACTS DEPLOYED:",response) 28 | this.setState({contracts:response.data}) 29 | }) 30 | .catch((error)=>{ 31 | console.log(error); 32 | }); 33 | } 34 | render() { 35 | 36 | if(!this.state.contracts){ 37 | return (
) 38 | } 39 | 40 | let contractDisplay = this.state.contracts.map((contract)=>{ 41 | if(contract){ 42 | return ( 43 |
44 | ) 45 | } 46 | }) 47 | 48 | 49 | return ( 50 | 51 | {contractDisplay} 52 | 53 | ); 54 | } 55 | } 56 | 57 | export default allBouncers; 58 | -------------------------------------------------------------------------------- /src/components/backend.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Blockie, Scaler, Button } from "dapparatus" 3 | import axios from 'axios'; 4 | 5 | let pollInterval 6 | let pollTime = 1501 7 | 8 | class Backend extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | sigs: [] 13 | } 14 | } 15 | componentDidMount(){ 16 | // pollInterval = setInterval(this.load.bind(this),pollTime) 17 | // this.load() 18 | // 19 | 20 | 21 | this.props.ipfsSigs.on('message', async (message) => { 22 | 23 | let data = JSON.parse(message.data) 24 | console.log(data) 25 | this.state.sigs.push(data) 26 | this.setState({sigs:this.state.sigs},()=>{ 27 | console.log("SIGS",this.state.sigs) 28 | }) 29 | }) 30 | 31 | } 32 | componentWillUnmount(){ 33 | // clearInterval(pollInterval) 34 | } 35 | /*load(){ 36 | axios.get(this.props.backendUrl+"sigs/"+this.props.address) 37 | .then((response)=>{ 38 | //console.log(response) 39 | this.setState({sigs:response.data}) 40 | }) 41 | .catch((error)=>{ 42 | console.log(error); 43 | }); 44 | }*/ 45 | async signContract() { 46 | let timestamp = Date.now() 47 | let message = ""+this.props.account+" trusts bouncer proxy "+this.props.address+" at "+timestamp 48 | console.log("sign",message) 49 | let sig = await this.props.web3.eth.personal.sign(message, this.props.account) 50 | console.log("SIG",sig) 51 | let data = JSON.stringify({ 52 | address:this.props.address, 53 | account:this.props.account, 54 | timestamp:timestamp, 55 | message:message, 56 | sig:sig 57 | }) 58 | console.log("BROADCASTING",data) 59 | this.props.ipfsSigs.broadcast(data) 60 | } 61 | render() { 62 | 63 | let sigs = [] 64 | //console.log("this.state.sigs",this.state.sigs) 65 | if(this.state.sigs){ 66 | for(let s in this.state.sigs){ 67 | let sig = this.state.sigs[s].account 68 | sigs.push( 69 | { 70 | console.log("updateBouncer",sig) 71 | this.props.updateBouncer(sig) 72 | }}> 73 | 74 | 75 | ) 76 | } 77 | } 78 | 79 | return ( 80 |
81 | 82 |
83 | {sigs} 84 |
85 | 88 |
89 |
90 | ); 91 | } 92 | } 93 | 94 | export default Backend; 95 | -------------------------------------------------------------------------------- /src/components/bouncer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Metamask, Gas, ContractLoader, Transactions, Events, Scaler, Blockie, Button } from "dapparatus" 3 | import { soliditySha3 } from 'web3-utils'; 4 | import axios from 'axios'; 5 | 6 | 7 | let pollInterval 8 | let pollTime = 509 9 | 10 | class Bouncer extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | count: "loading...", 15 | gasLimit: 120000 16 | } 17 | } 18 | handleInput(e){ 19 | let update = {} 20 | update[e.target.name] = e.target.value 21 | this.setState(update) 22 | } 23 | componentDidMount(){ 24 | pollInterval = setInterval(this.loadCount.bind(this),pollTime) 25 | this.loadCount() 26 | } 27 | componentWillUnmount(){ 28 | clearInterval(pollInterval) 29 | } 30 | async loadCount(){ 31 | let {contracts} = this.props 32 | let result = await contracts.Example.count().call() 33 | this.setState({count:result}) 34 | } 35 | addAmountMeta(){ 36 | let {contracts,contract,account} = this.props 37 | var data = contracts.Example.addAmount(5).encodeABI() 38 | console.log("DATA:",data) 39 | this.sendMetaTx(contract._address,account,contracts.Example._address,0,data) 40 | } 41 | sendEther(){ 42 | let {contract,account,web3} = this.props 43 | const wei = web3.utils.toWei(this.state.sendEther+"", 'ether') 44 | console.log("SENDING WEI:",wei) 45 | this.setState({sendEther:"",toAddress:""}) 46 | this.sendMetaTx(contract._address,account,this.state.toAddress,wei,"0x00") 47 | } 48 | async sendToken(){ 49 | let {contracts,contract,account,web3} = this.props 50 | var data = contracts.SomeToken.transfer(this.state.tokenToAddress,this.state.sendToken).encodeABI() 51 | console.log("DATA:",data) 52 | console.log("SENDING ",this.state.sendToken," tokens at address "+this.state.sendTokenAddress+" to address "+this.state.tokenToAddress) 53 | this.setState({sendToken:"",sendTokenAddress:"",tokenToAddress:""}) 54 | this.sendMetaTx(contract._address,account,this.state.sendTokenAddress,0,data) 55 | } 56 | async sendMetaTx(proxyAddress,fromAddress,toAddress,value,txData){ 57 | let {contract,account,web3} = this.props 58 | const nonce = await contract.nonce(fromAddress).call() 59 | console.log("Current nonce for "+fromAddress+" is ",nonce) 60 | let rewardAddress = "0x0000000000000000000000000000000000000000" 61 | let rewardAmount = 0 62 | if(this.state.rewardTokenAddress){ 63 | if(this.state.rewardTokenAddress=="1"||this.state.rewardTokenAddress=="0x0000000000000000000000000000000000000001"){ 64 | rewardAddress = "0x0000000000000000000000000000000000000001" 65 | this.setState({rewardTokenAddress:rewardAddress}) 66 | rewardAmount = web3.utils.toWei(this.state.rewardToken+"", 'ether') 67 | console.log("rewardAmount",rewardAmount) 68 | }else{ 69 | rewardAddress = this.state.rewardTokenAddress 70 | rewardAmount = this.state.rewardToken 71 | } 72 | } 73 | 74 | console.log("Reward: "+rewardAmount+" tokens at address "+rewardAddress) 75 | const parts = [ 76 | proxyAddress, 77 | fromAddress, 78 | toAddress, 79 | web3.utils.toTwosComplement(value), 80 | txData, 81 | rewardAddress, 82 | /*web3.utils.toTwosComplement(rewardAmount),*/ 83 | web3.utils.toTwosComplement(rewardAmount), 84 | web3.utils.toTwosComplement(nonce), 85 | ] 86 | /*web3.utils.padLeft("0x"+nonce,64),*/ 87 | console.log("PARTS",parts) 88 | const hashOfMessage = soliditySha3(...parts); 89 | const message = hashOfMessage 90 | console.log("sign",message) 91 | let sig = await this.props.web3.eth.personal.sign(""+message,account) 92 | console.log("SIG",sig) 93 | let postData = { 94 | gas: this.state.gasLimit, 95 | message: message, 96 | parts:parts, 97 | sig:sig, 98 | } 99 | /*axios.post(this.props.backendUrl+'tx', postData, { 100 | headers: { 101 | 'Content-Type': 'application/json', 102 | } 103 | }).then((response)=>{ 104 | console.log("TX RESULT",response) 105 | }) 106 | .catch((error)=>{ 107 | console.log(error); 108 | });*/ 109 | console.log("broadcast",postData) 110 | this.props.ipfs.broadcast(JSON.stringify(postData)) 111 | } 112 | render() { 113 | return ( 114 | 115 |
116 |
117 |
118 |
119 | Example Contract Count: {this.state.count} 120 |
121 |
122 | 125 |
126 |
127 |
128 | Send 129 | ether to 136 | 139 |
140 |
141 | Send 142 | of token to address 152 | 155 | 156 |
157 | Gas Limit: 161 |
162 |
163 | Reward: 164 | of token 171 |
172 | 173 |
174 |
175 |
176 | 177 | ); 178 | } 179 | } 180 | 181 | export default Bouncer; 182 | -------------------------------------------------------------------------------- /src/components/miner.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Address, Scaler } from "dapparatus" 3 | import { soliditySha3 } from 'web3-utils'; 4 | import axios from 'axios'; 5 | 6 | let pollInterval 7 | let pollTime = 509 8 | 9 | class Bouncer extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | address:false, 14 | } 15 | } 16 | componentDidMount(){ 17 | // pollInterval = setInterval(this.load.bind(this),pollTime) 18 | // this.load() 19 | this.props.ipfsMiners.on('message', async (message) => { 20 | let data = JSON.parse(message.data) 21 | console.log("MINER",data) 22 | this.setState({address:data.address}) 23 | }) 24 | } 25 | componentWillUnmount(){ 26 | // clearInterval(pollInterval) 27 | } 28 | /*async load(){ 29 | axios.get(this.props.backendUrl+"miner") 30 | .then((response)=>{ 31 | //console.log(response) 32 | this.setState({address:response.data.address}) 33 | }) 34 | .catch((error)=>{ 35 | console.log(error); 36 | }); 37 | }*/ 38 | render() { 39 | 40 | let address = "Loading..." 41 | if(this.state.address){ 42 | address = ( 43 |
47 | ) 48 | } 49 | return ( 50 |
51 | 52 | {address} 53 | 54 |
55 | ); 56 | } 57 | } 58 | 59 | export default Bouncer; 60 | -------------------------------------------------------------------------------- /src/components/owner.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Metamask, Gas, ContractLoader, Transactions, Events, Scaler, Blockie, Button } from "dapparatus" 3 | 4 | class Owner extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | addBouncer:"", 9 | removeBouncer:"", 10 | bouncers:[], 11 | removebouncers:[] 12 | } 13 | } 14 | handleBouncer(e){ 15 | this.props.updateBouncer(e.target.value) 16 | } 17 | addBouncer(){ 18 | let {tx,contract} = this.props 19 | console.log("Add Bouncer ",this.props.bouncer) 20 | tx(contract.updateWhitelist(this.props.bouncer,true),55000,(receipt)=>{ 21 | console.log("~~~~ BOUNCER ADDED:",receipt) 22 | }) 23 | } 24 | removeBouncer(){ 25 | let {tx,contract} = this.props 26 | console.log("Remove Bouncer ",this.props.bouncer) 27 | tx(contract.updateWhitelist(this.props.bouncer,false),55000) 28 | } 29 | render() { 30 | 31 | let bouncers = "" 32 | if(this.state.bouncers){ 33 | bouncers=this.state.bouncers.map((bouncer)=>{ 34 | return ( 35 |
36 | {bouncer} 37 |
38 | ) 39 | }) 40 | } 41 | 42 | let bouncerBlockie 43 | if(this.props.bouncer && this.props.bouncer.length){ 44 | bouncerBlockie = this.props.bouncer.toLowerCase() 45 | } 46 | console.log("bouncerBlockie",bouncerBlockie) 47 | if(!bouncerBlockie || bouncerBlockie.length<=0){ 48 | bouncerBlockie = "0x0000000000000000000000000000000000000000" 49 | } 50 | console.log("bouncerBlockie2",bouncerBlockie) 51 | return ( 52 |
53 | 54 |
55 | 59 | 62 | 65 | 68 |
69 | {bouncers} 70 |
71 | { 77 | console.log("UpdateWhitelist",eventData) 78 | this.state.bouncers.push(eventData._account.toLowerCase()) 79 | let update = {bouncers:this.state.bouncers} 80 | this.setState(update) 81 | this.props.onUpdate(update) 82 | }} 83 | /> 84 |
85 | { 91 | console.log("Forwarded",eventData) 92 | //this.setState({roleAddedEvents:allEvents.reverse()}) 93 | }} 94 | /> 95 |
96 |
97 | ); 98 | } 99 | } 100 | 101 | export default Owner; 102 | -------------------------------------------------------------------------------- /src/images/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austintgriffith/bouncer-proxy/7bbbb616f5d46094fa5fbf16c7cdb77d03b12bb8/src/images/qr.png -------------------------------------------------------------------------------- /src/images/videopreview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/austintgriffith/bouncer-proxy/7bbbb616f5d46094fa5fbf16c7cdb77d03b12bb8/src/images/videopreview.jpg -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker stop clevis 3 | -------------------------------------------------------------------------------- /tests/clevis.js: -------------------------------------------------------------------------------- 1 | const clevis = require("clevis") 2 | const colors = require('colors') 3 | const chai = require("chai") 4 | const assert = chai.assert 5 | const expect = chai.expect; 6 | const should = chai.should(); 7 | const fs = require('fs') 8 | const Web3 = require('web3') 9 | const clevisConfig = JSON.parse(fs.readFileSync("clevis.json").toString().trim()) 10 | web3 = new Web3(new Web3.providers.HttpProvider(clevisConfig.provider)) 11 | function localContractAddress(contract){ 12 | return fs.readFileSync(contract+"/"+contract+".address").toString().trim() 13 | } 14 | function localContractAbi(contract){ 15 | return JSON.parse(fs.readFileSync(contract+"/"+contract+".abi").toString().trim()) 16 | } 17 | function printTxResult(result){ 18 | if(!result||!result.transactionHash){ 19 | console.log("ERROR".red,"MISSING TX HASH".yellow) 20 | }else{ 21 | console.log(tab,result.transactionHash.gray,(""+result.gasUsed).yellow) 22 | } 23 | } 24 | function bigHeader(str){ 25 | return "########### "+str+" "+Array(128-str.length).join("#") 26 | } 27 | function rand(min, max) { 28 | return Math.floor( Math.random() * (max - min) + min ); 29 | } 30 | function getPaddedHexFromNumber(num,digits){ 31 | let hexIs = web3.utils.numberToHex(num).replace("0x",""); 32 | while(hexIs.length{ 45 | describe('#reload() ', function() { 46 | it('should force browser to reload', async function() { 47 | fs.writeFileSync("public/reload.txt",Date.now()); 48 | }); 49 | }); 50 | }, 51 | version:()=>{ 52 | describe('#version() ', function() { 53 | it('should get version', async function() { 54 | this.timeout(90000) 55 | const result = await clevis("version") 56 | console.log(result) 57 | }); 58 | }); 59 | }, 60 | blockNumber:()=>{ 61 | describe('#blockNumber() ', function() { 62 | it('should get blockNumber', async function() { 63 | this.timeout(90000) 64 | const result = await clevis("blockNumber") 65 | console.log(result) 66 | }); 67 | }); 68 | }, 69 | compile:(contract)=>{ 70 | describe('#compile() '+contract.magenta, function() { 71 | it('should compile '+contract.magenta+' contract to bytecode', async function() { 72 | this.timeout(90000) 73 | const result = await clevis("compile",contract) 74 | console.log(result) 75 | assert(Object.keys(result.contracts).length>0, "No compiled contacts found.") 76 | let count = 0 77 | for(let c in result.contracts){ 78 | console.log("\t\t"+"contract "+c.blue+": ",result.contracts[c].bytecode.length) 79 | if(count++==0){ 80 | assert(result.contracts[c].bytecode.length > 1, "No bytecode for contract "+c) 81 | } 82 | } 83 | }); 84 | }); 85 | }, 86 | deploy:(contract,accountindex)=>{ 87 | describe('#deploy() '+contract.magenta, function() { 88 | it('should deploy '+contract.magenta+' as account '+accountindex, async function() { 89 | this.timeout(360000) 90 | const result = await clevis("deploy",contract,accountindex) 91 | printTxResult(result) 92 | console.log(tab+"Address: "+result.contractAddress.blue) 93 | assert(result.contractAddress) 94 | }); 95 | }); 96 | }, 97 | 98 | publish:()=>{ 99 | describe('#publish() ', function() { 100 | it('should inject contract address and abi into web app', async function() { 101 | this.timeout(120000) 102 | const fs = require("fs") 103 | if(!fs.existsSync("src")){ 104 | fs.mkdirSync("src"); 105 | } 106 | if(!fs.existsSync("src/contracts")){ 107 | fs.mkdirSync("src/contracts"); 108 | } 109 | for(let c in module.exports.contracts){ 110 | let thisContract = module.exports.contracts[c] 111 | console.log(tab,thisContract.magenta) 112 | let address = fs.readFileSync(thisContract+"/"+thisContract+".address").toString().trim() 113 | console.log(tab,"ADDRESS:",address.blue) 114 | assert(address,"No Address!?") 115 | fs.writeFileSync("src/contracts/"+thisContract+".address.js","module.exports = \""+address+"\""); 116 | let blockNumber = fs.readFileSync(thisContract+"/"+thisContract+".blockNumber").toString().trim() 117 | console.log(tab,"blockNumber:",blockNumber.blue) 118 | assert(blockNumber,"No blockNumber!?") 119 | fs.writeFileSync("src/contracts/"+thisContract+".blocknumber.js","module.exports = \""+blockNumber+"\""); 120 | let abi = fs.readFileSync(thisContract+"/"+thisContract+".abi").toString().trim() 121 | fs.writeFileSync("src/contracts/"+thisContract+".abi.js","module.exports = "+abi); 122 | let bytecode = fs.readFileSync(thisContract+"/"+thisContract+".bytecode").toString().trim() 123 | fs.writeFileSync("src/contracts/"+thisContract+".bytecode.js","module.exports = \""+bytecode+"\""); 124 | } 125 | fs.writeFileSync("src/contracts/contracts.js","module.exports = "+JSON.stringify(module.exports.contracts)); 126 | module.exports.reload() 127 | }); 128 | }); 129 | }, 130 | metamask:()=>{ 131 | describe('#transfer() ', function() { 132 | it('should give metamask account some ether or tokens to test', async function() { 133 | this.timeout(600000) 134 | let result = await clevis("sendTo","0.1","0","0x2a906694D15Df38F59e76ED3a5735f8AAbccE9cb") 135 | printTxResult(result) 136 | result = await clevis("sendTo","0.1","0","0x5f19cefc9c9d1bc63f9e4d4780493ff5577d238b") 137 | printTxResult(result) 138 | 139 | //here is an example of running a funtion from within this object: 140 | //module.exports.mintTo("Greens",0,"0x2a906694d15df38f59e76ed3a5735f8aabcce9cb",20) 141 | //view more examples here: https://github.com/austintgriffith/galleass/blob/master/tests/galleass.js 142 | }); 143 | }); 144 | }, 145 | 146 | ////----------------------------------------------------------------------------/////////////////// 147 | 148 | 149 | addBouncer:(accountIndex,bouncerAccountIndex)=>{ 150 | describe('#addBouncer', function() { 151 | it('should add account with index bouncerAccountIndex as a bouncer', async function() { 152 | this.timeout(600000) 153 | 154 | const accounts = await clevis("accounts") 155 | 156 | const result = await clevis("contract","addBouncer","BouncerProxy",accountIndex,accounts[bouncerAccountIndex]) 157 | printTxResult(result) 158 | }); 159 | }); 160 | }, 161 | fwd:(accountIndexSender,accountIndexSigner)=>{ 162 | describe('#fwd', function() { 163 | it('should build meta transaction into data, sign it as accountIndexSigner and send it as accountIndexSender ', async function() { 164 | this.timeout(600000) 165 | 166 | const accounts = await clevis("accounts") 167 | 168 | let testAbi = localContractAbi("Example") 169 | let testAddress = localContractAddress("Example") 170 | var data = (new web3.eth.Contract(testAbi,testAddress)).methods.addAmount(5).encodeABI() 171 | console.log("DATA:",data) 172 | //const result = await clevis("contract","forward","TEst",accountIndex,localContractAddress("Example"),"0",data) 173 | //printTxResult(result) 174 | 175 | const nonce = await clevis("contract","nonce","BouncerProxy",accounts[accountIndexSigner]) 176 | 177 | console.log("Current nonce for "+accounts[accountIndexSigner]+" is ",nonce) 178 | 179 | const { soliditySha3 } = require('web3-utils'); 180 | 181 | const rewardAddress = "0x0000000000000000000000000000000000000000" 182 | 183 | const reqardAmount = 0 184 | 185 | //keccak256(abi.encodePacked(address(this), signer, destination, value, data, nonce[signer])), 186 | const parts = [ 187 | localContractAddress("BouncerProxy"), 188 | accounts[accountIndexSigner], 189 | localContractAddress("Example"), 190 | web3.utils.toTwosComplement(0), 191 | data, 192 | rewardAddress, 193 | web3.utils.toTwosComplement(reqardAmount), 194 | web3.utils.toTwosComplement(nonce), 195 | ] 196 | console.log("PARTS",parts) 197 | const hashOfMessage = soliditySha3(...parts); 198 | 199 | const message = hashOfMessage 200 | 201 | let sig = await web3.eth.sign(message, accounts[accountIndexSigner]) 202 | 203 | console.log("message:"+message+" sig:",sig) 204 | 205 | //function forward(bytes sig, address signer, address destination, uint value, bytes data) public { 206 | const result = await clevis("contract","forward","BouncerProxy",accountIndexSender,sig,accounts[accountIndexSigner],localContractAddress("Example"),"0",data,rewardAddress,reqardAmount) 207 | printTxResult(result) 208 | }); 209 | }); 210 | }, 211 | fwdAndPaySomeToken:(accountIndexSender,accountIndexSigner)=>{ 212 | describe('#fwdAndPaySomeToken', function() { 213 | it('should build meta transaction into data, sign it as accountIndexSigner and send it as accountIndexSender ', async function() { 214 | this.timeout(600000) 215 | 216 | const accounts = await clevis("accounts") 217 | 218 | let testAbi = localContractAbi("Example") 219 | let testAddress = localContractAddress("Example") 220 | var data = (new web3.eth.Contract(testAbi,testAddress)).methods.addAmount(5).encodeABI() 221 | console.log("DATA:",data) 222 | 223 | const nonce = await clevis("contract","nonce","BouncerProxy",accounts[accountIndexSigner]) 224 | 225 | console.log("Current nonce for "+accounts[accountIndexSigner]+" is ",nonce) 226 | 227 | const { soliditySha3 } = require('web3-utils'); 228 | 229 | const rewardAddress = localContractAddress("SomeToken") 230 | 231 | const rewardAmount = 9 232 | 233 | //keccak256(abi.encodePacked(address(this), signer, destination, value, data, nonce[signer])), 234 | const parts = [ 235 | localContractAddress("BouncerProxy"), 236 | accounts[accountIndexSigner], 237 | localContractAddress("Example"), 238 | web3.utils.toTwosComplement(0), 239 | data, 240 | rewardAddress, 241 | web3.utils.toTwosComplement(rewardAmount), 242 | web3.utils.toTwosComplement(nonce), 243 | ] 244 | console.log("PARTS",parts) 245 | const hashOfMessage = soliditySha3(...parts); 246 | 247 | const message = hashOfMessage 248 | 249 | let sig = await web3.eth.sign(message, accounts[accountIndexSigner]) 250 | 251 | console.log("message:"+message+" sig:",sig) 252 | 253 | //function forward(bytes sig, address signer, address destination, uint value, bytes data) public { 254 | const result = await clevis("contract","forward","BouncerProxy",accountIndexSender,sig,accounts[accountIndexSigner],localContractAddress("Example"),"0",data,rewardAddress,rewardAmount) 255 | printTxResult(result) 256 | }); 257 | }); 258 | }, 259 | 260 | 261 | mintSomeToken:(accountIndex,toAccountIndex,amount)=>{ 262 | describe('#mintSomeToken', function() { 263 | it('should mint SomeToken to toAccountIndex', async function() { 264 | this.timeout(600000) 265 | 266 | const accounts = await clevis("accounts") 267 | const result = await clevis("contract","Mint","SomeToken",accountIndex,accounts[toAccountIndex],amount) 268 | printTxResult(result) 269 | }); 270 | }); 271 | }, 272 | approveBouncerProxy:(accountIndex,amount)=>{ 273 | describe('#approveBouncerProxy', function() { 274 | it('should approve BouncerProxy to transfer SomeToken', async function() { 275 | this.timeout(600000) 276 | const result = await clevis("contract","approve","SomeToken",accountIndex,localContractAddress("BouncerProxy"),amount) 277 | printTxResult(result) 278 | }); 279 | }); 280 | }, 281 | 282 | ////----------------------------------------------------------------------------/////////////////// 283 | 284 | 285 | full:()=>{ 286 | describe(bigHeader('COMPILE'), function() { 287 | it('should compile all contracts', async function() { 288 | this.timeout(6000000) 289 | const result = await clevis("test","compile") 290 | assert(result==0,"deploy ERRORS") 291 | }); 292 | }); 293 | describe(bigHeader('FAST'), function() { 294 | it('should run the fast test (everything after compile)', async function() { 295 | this.timeout(6000000) 296 | const result = await clevis("test","fast") 297 | assert(result==0,"fast ERRORS") 298 | }); 299 | }); 300 | }, 301 | 302 | fast:()=>{ 303 | describe(bigHeader('DEPLOY'), function() { 304 | it('should deploy all contracts', async function() { 305 | this.timeout(6000000) 306 | const result = await clevis("test","deploy") 307 | assert(result==0,"deploy ERRORS") 308 | }); 309 | }); 310 | describe(bigHeader('METAMASK'), function() { 311 | it('should deploy all contracts', async function() { 312 | this.timeout(6000000) 313 | const result = await clevis("test","metamask") 314 | assert(result==0,"metamask ERRORS") 315 | }); 316 | }); 317 | describe(bigHeader('PUBLISH'), function() { 318 | it('should publish all contracts', async function() { 319 | this.timeout(6000000) 320 | const result = await clevis("test","publish") 321 | assert(result==0,"publish ERRORS") 322 | }); 323 | }); 324 | 325 | }, 326 | 327 | } 328 | 329 | checkContractDeployment = async (contract)=>{ 330 | const localAddress = localContractAddress(contract) 331 | const address = await clevis("contract","getContract","Example",web3.utils.fromAscii(contract)) 332 | console.log(tab,contract.blue+" contract address is "+(localAddress+"").magenta+" deployed as: "+(address+"").magenta) 333 | assert(localAddress==address,contract.red+" isn't deployed correctly!?") 334 | return address 335 | } 336 | 337 | /* 338 | makeSureContractHasTokens = async (contract,contractAddress,token)=>{ 339 | const TokenBalance = await clevis("contract","balanceOf",token,contractAddress) 340 | console.log(tab,contract.magenta+" has "+TokenBalance+" "+token) 341 | assert(TokenBalance>0,contract.red+" doesn't have any "+token.red) 342 | } 343 | 344 | view more examples here: https://github.com/austintgriffith/galleass/blob/master/tests/galleass.js 345 | 346 | */ 347 | -------------------------------------------------------------------------------- /tests/compile.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | for(let c in clevis.contracts){ 3 | clevis.compile(clevis.contracts[c]) 4 | } 5 | -------------------------------------------------------------------------------- /tests/deploy.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | for(let c in clevis.contracts){ 3 | clevis.deploy(clevis.contracts[c],0) 4 | } 5 | -------------------------------------------------------------------------------- /tests/example.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | run from parent directory: 4 | 5 | mocha tests/account.js 6 | 7 | */ 8 | const clevis = require("clevis") 9 | const colors = require('colors') 10 | const chai = require("chai") 11 | const assert = chai.assert 12 | const expect = chai.expect; 13 | const should = chai.should(); 14 | 15 | 16 | //--------------------------------------------------------// 17 | 18 | testAccounts() 19 | //testContractCompile("Contract") 20 | //testContractDeploy("Contract",0) 21 | 22 | //--------------------------------------------------------// 23 | 24 | function testAccounts(){ 25 | describe('#accounts()', function() { 26 | it('should have at least one accounts to work with', async function() { 27 | const accounts = await clevis("accounts") 28 | console.log(accounts) 29 | assert(accounts.length > 0) 30 | }); 31 | }); 32 | } 33 | 34 | function testContractCompile(contract){ 35 | const tab = "\t\t"; 36 | describe('#compile() '+contract.magenta, function() { 37 | it('should compile '+contract.magenta+' contract to bytecode', async function() { 38 | this.timeout(10000) 39 | const result = await clevis("compile",contract) 40 | for(let c in result.contracts){ 41 | console.log("\t\t"+"contract "+c.blue+": ",result.contracts[c].bytecode.length) 42 | assert(result.contracts[c].bytecode.length > 1) 43 | } 44 | }); 45 | }); 46 | } 47 | 48 | function testContractDeploy(contract,accountindex){ 49 | const tab = "\t\t"; 50 | describe('#deploy() '+contract.magenta, function() { 51 | it('should deploy '+contract.magenta+' as account '+accountindex, async function() { 52 | this.timeout(60000) 53 | const result = await clevis("deploy",contract,accountindex) 54 | console.log(tab+"Address: "+result.contractAddress.blue) 55 | assert(result.contractAddress) 56 | }); 57 | }); 58 | } 59 | 60 | // -------------------- example contract logic tests ---------------------------------------- // 61 | 62 | function accountCanSetName(contract,account,name){ 63 | const tab = "\t\t"; 64 | describe('#testCanSetName() '+contract.magenta, function() { 65 | it('should set the name of '+contract.magenta+' as account '+account, async function() { 66 | this.timeout(10000) 67 | 68 | let setResult = await clevis("contract","setName",contract,account,name) 69 | //console.log(setResult) 70 | assert(setResult.status == 1) 71 | console.log(tab+"Status: "+setResult.status.toString().blue) 72 | 73 | let gotName = await clevis("contract","name",contract) 74 | assert(gotName == name) 75 | console.log(tab+"Name: "+gotName.blue) 76 | 77 | }); 78 | }); 79 | } 80 | 81 | function accountCanNOTSetName(contract,account,name){ 82 | const tab = "\t\t"; 83 | describe('#testCanNOTSetName() '+contract.magenta, function() { 84 | it('should fail to set the name of '+contract.magenta+' as account '+account+" with a 'revert' error", async function() { 85 | this.timeout(10000) 86 | //let revertError = new Error("Error: Returned error: VM Exception while processing transaction: revert") 87 | //expect( await clevis("contract","setName",contract,account,name) ).to.be.rejectedWith('revert'); 88 | let error 89 | try{ 90 | await clevis("contract","setName",contract,account,name) 91 | console.log(tab,"WARNING".red,"WAS ABLE TO SET!".yellow) 92 | }catch(e){ 93 | error = e.toString() 94 | } 95 | assert(error.indexOf("VM Exception while processing transaction: revert")>0) 96 | }); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /tests/fast.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.fast() 3 | -------------------------------------------------------------------------------- /tests/full.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.full() 3 | -------------------------------------------------------------------------------- /tests/metamask.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.metamask() 3 | -------------------------------------------------------------------------------- /tests/publish.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.publish() 3 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | 3 | //add account with index 1 as a bouncer 4 | // this means they can sign meta transactions that any other account can pay to submit 5 | clevis.addBouncer(0,1) 6 | 7 | //mint SomeToken to account 1 from account 0 (this will be used to incentive other accounts to send the meta trasaction) 8 | clevis.mintSomeToken(0,1,99); 9 | 10 | //account 1 needs approve the BouncerProxy to move SomeToken around 11 | clevis.approveBouncerProxy(1,99); 12 | 13 | 14 | //use account 2 to send a tx to the Example contract as account 1 15 | //clevis.fwd(2,1) 16 | clevis.fwdAndPaySomeToken(2,1) 17 | -------------------------------------------------------------------------------- /tests/version.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.version() 3 | --------------------------------------------------------------------------------