浏览代码

Refactor & Restart. Let there be tokens.

Simon de la Rouviere 8 年之前
当前提交
e9817a3cae

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+bower_components
+.DS_Store
+*.swp

+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+# Tokens
+
+This is a repo containing various contracts, templates & full-blown dapps related to token systems on Ethereum. It will be fleshed out over time.
+
+Contracts contains Solidity contracts, ready to be built upon.  
+Templates contain front-end components, where the developer can change basic parameters for a token system and then deploy it themselves, having a skin available.  
+Projects are more full-blown dapps developed by ConsenSys for use in Ethereum directly related to token systems (such as a dapp factory).

+ 3 - 0
contracts/README.md

@@ -0,0 +1,3 @@
+# Contracts
+
+This is a directory of Token contracts for various uses cases. From simplistic, standardised ones to complex ones with modular hook functionality. To be fleshed out.

+ 3 - 0
projects/README.md

@@ -0,0 +1,3 @@
+# Projects
+
+This directory contains full blown dapps that are deployed on Ethereum. The first is a factory that allows people to craft their own tokens without having to fork templates.

+ 3 - 0
templates/README.md

@@ -0,0 +1,3 @@
+# Templates
+
+This directory contains already completed dapps that act as templates. Fork, change parameters (such as name & images to use), deploy contracts, set address of contract and you are set. Each template has its own instructions on what is changeable/forkable.

+ 4 - 0
templates/personal_token/.gitignore

@@ -0,0 +1,4 @@
+bower_components
+.DS_Store
+*.swp
+reactor

+ 8 - 0
templates/personal_token/README.md

@@ -0,0 +1,8 @@
+# Personal Token 
+
+This is a simple token template for a personal token. The idea is that you can replace the config.json with your name, image and address of the token contract. It must follow a similar format in order to properly scaffold the input form (notably the "sendToken" function).
+
+It uses React in the front-end currently to scaffold this form/input. Perhaps slightly overkill at the moment, but useful for future templating.
+
+It uses Bower to install the front-end components. Will flesh out proper installation/tutorial soon.
+

+ 8 - 0
templates/personal_token/bower.json

@@ -0,0 +1,8 @@
+{
+    "name": "vinay_coin",
+    "dependencies": {
+        "bootstrap": "~3.2.0",
+        "web3": "develop",
+        "react": "0.13.1"
+    }
+}

+ 4 - 0
templates/personal_token/config.json

@@ -0,0 +1,4 @@
+{
+    "token_name": "Vinay's Hexatokens",
+    "token_image": "static/example_vinay.jpg"
+} 

+ 63 - 0
templates/personal_token/contracts/Token.abi

@@ -0,0 +1,63 @@
+[
+   {
+      "constant" : true,
+      "inputs" : [
+         {
+            "name" : "",
+            "type" : "address"
+         }
+      ],
+      "name" : "balances",
+      "outputs" : [
+         {
+            "name" : "",
+            "type" : "uint256"
+         }
+      ],
+      "type" : "function"
+   },
+   {
+      "constant" : false,
+      "inputs" : [
+         {
+            "name" : "_to",
+            "type" : "address"
+         },
+         {
+            "name" : "_amount",
+            "type" : "uint256"
+         }
+      ],
+      "name" : "sendToken",
+      "outputs" : [],
+      "type" : "function"
+   },
+   {
+      "constant" : false,
+      "inputs" : [
+         {
+            "name" : "_to",
+            "type" : "address"
+         },
+         {
+            "name" : "_amount",
+            "type" : "uint256"
+         }
+      ],
+      "name" : "createToken",
+      "outputs" : [],
+      "type" : "function"
+   },
+   {
+      "constant" : true,
+      "inputs" : [],
+      "name" : "owner",
+      "outputs" : [
+         {
+            "name" : "",
+            "type" : "address"
+         }
+      ],
+      "type" : "function"
+   }
+]

+ 28 - 0
templates/personal_token/contracts/Token.sol

@@ -0,0 +1,28 @@
+contract Token {
+    
+    function Token() {
+        owner = msg.sender;
+    }
+    
+    modifier isOwner {
+        if (msg.sender == owner) {
+            _
+        }
+    }
+    
+    //only owner can currently create new tokens.
+    //change as needed.
+    function createToken(address _to, uint _amount) isOwner {
+        balances[_to] += _amount; //check if can be done from unitialised acc
+    }
+    
+    function sendToken(address _to, uint _amount) {
+        if (balances[msg.sender] >= _amount) {
+            balances[msg.sender] -= _amount;
+            balances[_to] += _amount;
+        }
+    }
+    
+    address public owner;
+    mapping (address => uint) public balances; //how does this work?
+}

+ 62 - 0
templates/personal_token/index.html

@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Contract Reactor (for Ethereum)</title>
+    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
+    <script src="bower_components/jquery/dist/jquery.min.js"></script>
+    <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
+    <script src="bower_components/bignumber.js/bignumber.min.js"></script>
+    <script src="bower_components/web3/dist/web3.js"></script><!--debugging-->
+    <script src="bower_components/react/react.js"></script><!--debugging-->
+    <script src="bower_components/react/JSXTransformer.js"></script>
+    <script>
+        if (typeof web3 === "undefined") {
+            web3 = require('web3');
+            window.web3 = web3;
+        }
+
+        //connect to either aleth/mist depending what is online. Prefer Mist as is supposed to be more usable one into the future.
+        //this looks ugly. There must be better convention.
+        try {
+            web3.setProvider(new web3.providers.HttpProvider('http://127.0.0.1:8536')); //mist port
+            console.log(web3.eth.blockNumber);
+            console.log("Connected to Mist/Ethereum on port 8545");
+        } catch(err) {
+            console.log("Mist's RPC port is not online, or the port has changed.");
+            try {
+                web3.setProvider(new web3.providers.HttpProvider('http://127.0.0.1:8080')); //cpp-port
+                console.log(web3.eth.blockNumber);
+                console.log("Connected to Aleth/cpp-eth on port 8080");
+            } catch(err) {
+                console.log("AlethZero's RPC port is not online, or the port has changed.");
+            }
+        }
+    </script>
+
+</head>
+
+<body>
+<div class="container">
+    <div id="top">
+    </div>
+    
+    <div id="contracts">
+    </div>
+
+</div>
+
+    <!-- Development Purposes. AlethZero is caching very aggressively -->
+    <!-- Run web root where reactor.jsx is. -->
+    <script>document.write('<script type="text/jsx" src="js/Header.jsx?dev=' + Math.floor(Math.random() * 1000) + '"\><\/script>');</script> 
+    <script>document.write('<script type="text/jsx" src="js/InputWrapper.jsx?dev=' + Math.floor(Math.random() * 1000) + '"\><\/script>');</script> 
+    <script>document.write('<script type="text/jsx" src="js/FunctionWrapper.jsx?dev=' + Math.floor(Math.random() * 1000) + '"\><\/script>');</script> 
+    <script>document.write('<script type="text/jsx" src="js/DeployWrapper.jsx?dev=' + Math.floor(Math.random() * 1000) + '"\><\/script>');</script> 
+    <script>document.write('<script type="text/jsx" src="js/ContractWrapper.jsx?dev=' + Math.floor(Math.random() * 1000) + '"\><\/script>');</script> 
+    <script>document.write('<script type="text/jsx" src="js/reactor.jsx?dev=' + Math.floor(Math.random() * 1000) + '"\><\/script>');</script> 
+    <script>document.write('<script type="text/jsx" src="js/script.jsx?dev=' + Math.floor(Math.random() * 1000) + '"\><\/script>');</script> 
+</body>
+
+</html>

+ 21 - 0
templates/personal_token/js/ContractWrapper.jsx

@@ -0,0 +1,21 @@
+//takes compiled code, instance and template.
+var ContractWrapper = React.createClass({
+    render: function() {
+        return (
+            <div>
+            <ul>
+                {this.props.compiled.info.abiDefinition.map(function(result) {
+                    if(result.type == "function") { //TODO: Determine whether events can be called from outside, otherwise it should be included.
+                        //react key = unique function name for contract.
+                        var function_template = {};
+                        if (this.props.contract_template.hasOwnProperty(result.name)) { //if a specific function has a template for it.
+                            function_template = this.props.contract_template[result.name];
+                        }
+                        return <FunctionWrapper function_template={function_template} instance={this.props.instance} key={result.name} data={result}/>;
+                    }
+                }, this)}
+            </ul>
+            </div>
+        );
+    }
+});

+ 41 - 0
templates/personal_token/js/DeployWrapper.jsx

@@ -0,0 +1,41 @@
+var DeployWrapper = React.createClass({
+    getInitialState: function() {
+        //check if contract exists
+        codeOnChain = web3.eth.getCode(this.props.instance.address);
+        var submitted = true;
+
+        /*
+        Currently, this check is a hack. Compiled code has a padded part in the front (that is supposedly the keccak hash of the bytecode).
+        However, haven't been able to understand or reproduce it from the yellow paper or the solidity compiler code.
+        The latter parts will match though. However, there could be scenarios where only parts of it matches and thus it could erroneously match.
+        At the very least, it should not be 0x.
+        */
+        if (this.props.compiled.code.indexOf(codeOnChain.substring(2)) == -1 || codeOnChain == "0x") {
+            submitted = false;
+            //console.log("Contract doesn't exist.");
+        }
+        return {
+           submitted : submitted,
+        }
+    },
+    deployContract: function() {
+        //use max gas for now.
+        var address = web3.eth.sendTransaction({gas: '3141592', from: web3.eth.accounts[0], data: this.props.compiled.code}); //TODO: change to include other accounts
+        console.log(address); //TODO: Add callback that cascades back and sets addresses for contracts.
+    },
+    render: function() {
+        if(this.state.submitted == true) {
+            return (
+                <div>
+                    Contract exists on the blockchain.   
+                </div>
+            );
+        } else {
+            return (
+                <div>
+                    Contract does not exist on the blockchain. <button className={"btn btn-default"} onClick={this.deployContract}>Deploy Contract {this.props.name}</button>
+                </div>
+            );
+        }
+    }
+});

+ 44 - 0
templates/personal_token/js/FunctionWrapper.jsx

@@ -0,0 +1,44 @@
+//takes instance and its part of the ABI + its part of the template
+var FunctionWrapper = React.createClass({
+    executeFunction: function(type) {
+        args = {};
+        //get inputs [replace with pure js as this requires jquery as a dependency]
+        $.each(this.refs, function(i, obj) {
+              args[obj.props.arg] = obj.state.value; //map inputs to a dictionary
+        });
+        var function_name = this.props.data.name.split("(")[0]; //seems very hacky to get only function name. It's written as 'function(args)' usually.
+        if(type == "call") {
+            result = this.props.instance[function_name].call(args);
+            console.log(result);
+        } else if(type == "transact") {
+            result = this.props.instance[function_name].sendTransaction(args);
+            console.log(result);
+        }
+    },
+    render: function() {
+        //use react refs to keep track of inputs to a function.
+        if(this.props.function_template.button != undefined) {
+            var button = <div><button className={"btn btn-default"} onClick={this.executeFunction.bind(this,"transact")}>{this.props.function_template.button}</button></div>;
+        } else {
+            var button = <div><button className={"btn btn-default"} onClick={this.executeFunction.bind(this,"call")}>Call() {this.props.data.name}</button> - <button className={"btn btn-default"} onClick={this.executeFunction.bind(this,"transact")}>Transact() {this.props.data.name}</button></div>;
+        }
+        return (
+        <div>
+            {this.props.data.inputs.map(function(result) {
+                var input_template = {};
+                var arg = result.name;
+                if (this.props.function_template.inputs.hasOwnProperty(result.name)) { //if a specific function has a template for it.
+                    input_template = this.props.function_template.inputs[result.name];
+                    arg = this.props.function_template.inputs[result.name].default_value;
+                }
+
+                return <div key={result.name}><InputWrapper input_template={input_template} ref={result.name} arg={arg} /></div>
+            }, this)}
+            <br />
+
+            {button}
+        </div>
+        );
+            
+    }
+});

+ 14 - 0
templates/personal_token/js/InputWrapper.jsx

@@ -0,0 +1,14 @@
+//takes reference & name for arguments
+var InputWrapper = React.createClass({
+    getInitialState: function() {
+        return {
+            value: ""
+        }
+    },
+    handleChange: function(event) {
+        this.setState({value: event.target.value});
+    },
+    render: function() {
+        return <div> {this.props.input_template.label} <input className={"form-control"} type="text" value={this.state.value} placeholder={this.props.arg} onChange={this.handleChange}/> </div>
+    }
+});

+ 16 - 0
templates/personal_token/js/header.jsx

@@ -0,0 +1,16 @@
+var Header = React.createClass({
+    getInitialState: function() {
+        return {
+            blockNumber: "", //todo, later for potentially adding contract creation as well.
+        }
+    },
+    render: function() {
+        console.log("tring to render");
+        return (
+        <div>
+            <h3 className={"text-center"} >{this.props.data.token_name} </h3>
+            <img src={this.props.data.token_image} className={"img-circle center-block"}/>
+        </div>
+        );
+    }
+});

+ 46 - 0
templates/personal_token/js/reactor.jsx

@@ -0,0 +1,46 @@
+//TODO: Add control panel. Starts mining & unlocks + result view (instead of JS console)
+//TODO: config.json doesn't feel 100% the best route to take... Potentially refactor into one, and allow rewriting of it (when addresses are created)
+//TODO: Templating should be derived from the .sol eventually.
+
+var Reactor = React.createClass({
+    render: function() {
+        return (
+        <div>
+            {Object.keys(this.props.compiled).map(function(result) { //iterate through multiple contracts based on keys
+                //console.log(this.props.compiled[result]);
+                //var abi = this.props.compiled[result].info.abiDefinition;
+                var contract = web3.eth.contract(this.props.compiled[result].info.abiDefinition);
+                var instance = contract.at(this.props.addresses[result]);
+                <hr/>
+                var contract_template = {};
+                var new_compiled = this.props.compiled;
+                var abi = this.props.compiled[result].info.abiDefinition;
+                if(this.props.options[result]["template_overlay"] == false) {
+                    //remove parts of the ABI.
+                    $.each(abi, function(i, obj) {
+                        if(obj.name != undefined) {
+                            //console.log(this.props.templates[result]);
+                            if(this.props.templates[result].hasOwnProperty(obj.name) == false) {
+                                console.log("deleting part of abi");
+                                console.log(abi[i]);
+                                delete abi[i];
+                            }
+                        }
+                    }.bind(this));
+                    console.log("false");
+                    
+                }
+
+                if(this.props.templates.hasOwnProperty(result)) {
+                    contract_template = this.props.templates[result]; //if a contract has a template.
+                }
+                return  (
+                    <div key={result}>
+                    <ContractWrapper key={result} name={result} contract_template={contract_template} compiled={this.props.compiled[result]} instance={instance} />
+                    </div>
+                )
+            }, this)}
+        </div>
+        );
+    }
+});

+ 77 - 0
templates/personal_token/js/script.jsx

@@ -0,0 +1,77 @@
+//EXAMPLE
+//helper function to get URL parameters.
+var urlParams;
+(window.onpopstate = function () {
+    var match,
+        pl     = /\+/g,  // Regex for replacing addition symbol with a space
+        search = /([^&=]+)=?([^&]*)/g,
+        decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+        query  = window.location.search.substring(1);
+
+    urlParams = {};
+    while (match = search.exec(query))
+       urlParams[decode(match[1])] = decode(match[2]);
+})();
+/*------------------*/
+
+var config;
+if("config" in urlParams) {
+    config = urlParams['config'];
+} else {
+    var config = 'reactor_config.json'; //random default
+}
+
+$.ajax({
+    url: config,
+    dataType: 'json',
+    cache: false,
+    error: function(data) {
+        console.log(data);
+    },
+    success: function(data) {
+        //map through multiple contracts (this includes multiple ones in 1 file + different files).
+        //console.log(data);
+        var total_compiled = {};
+        var addresses = {}; 
+        var templates = {};
+        var options = {};
+        Object.keys(data["contracts"]).map(function(contract_name) { //iterate through multiple contracts based on keys
+            $.ajax({
+                //fetch .sol and compile it, adding compiled result & its specified address to separate dictionaries
+                //3 parts: the compiled code from .sols. The address mapping. The templates.
+                url: data["contracts"][contract_name].path,
+                dataType: 'text',
+                cache: false,
+                async: false,
+                success: function(contract) {
+                    /*
+                    This is slightly "hacky". If one file has multiple contracts, it returns one dictionary.
+                    This concatenates them in the scenario where there are multiple files as well.
+                    */
+                    compiled = web3.eth.compile.solidity(contract);
+                    Object.keys(compiled).map(function(compiled_contract_name) {
+                        if(total_compiled.hasOwnProperty(compiled_contract_name) == false) { //not yet inserted
+                            addresses[compiled_contract_name] = data["contracts"][compiled_contract_name].address; //not sure why I've been doing [] & . notation here.
+                            templates[compiled_contract_name] = data["contracts"][compiled_contract_name].template;
+                            options[compiled_contract_name] = {"template_overlay": data["contracts"][compiled_contract_name].template_overlay};
+                        }
+                    });
+                    $.extend(total_compiled, compiled);
+                }
+            });
+        });
+        console.log(total_compiled); 
+        console.log(addresses); 
+        console.log(templates); 
+        console.log(options); 
+        $.ajax({
+              url: "../config.json",
+                dataType: 'json',
+                cache: false,
+        })
+        .done(function(data) {
+            React.render(<Header data={data} />, document.getElementById('top'));
+            React.render(<Reactor templates={templates} compiled={total_compiled} addresses={addresses} options={options}/>, document.getElementById('contracts'));
+        });
+    }
+});

+ 24 - 0
templates/personal_token/reactor_config.json

@@ -0,0 +1,24 @@
+{
+    "contracts": { 
+        "Token": {
+            "address": "0xbc72cf3079e08295364510917f92a10d0d54f9d2",
+            "path": "contracts/Token.sol",
+            "template_overlay": false,
+            "template": {
+                "sendToken": {
+                    "button": "Send Token",
+                    "inputs": {
+                        "_to": {
+                            "label": "Insert Address",
+                            "default_value": "eg 0xbc72cf3079e08295364510917f92a10d0d54f9d2"
+                        },
+                        "_amount": {
+                            "label": "Amount to send",
+                            "default_value": "eg 12"
+                        }
+                    }
+                }
+            }
+        }
+    }
+} 

二进制
templates/personal_token/static/example_vinay.jpg


+ 4 - 0
templates/personal_token/static/index.css

@@ -0,0 +1,4 @@
+.container { 
+}
+
+