美文网首页
开发第一个 Nervos Dapp

开发第一个 Nervos Dapp

作者: 水瓶座男生 | 来源:发表于2018-08-29 14:08 被阅读314次

    在Nervos AppChain环境下开发Dapp流程分为三步:

    • 开发前端应用;
    • 编写智能合约,智能合约包含了链上处理业务的所有逻辑;
    • 打通前端应用与链上智能合约的数据交互。

    1.安装create-react-app

    npm install -g create-react-app

    2.创建一个react脚手架工程

    create-react-app first-forever && cd first-forever

    // 安装结果
    Success! Created first_forever at C:\users\hadoop\AppData\Roaming\npm\first_forever
    Inside that directory, you can run several commands:
    
      yarn start
        Starts the development server.
    
      yarn build
        Bundles the app into static files for production.
    
      yarn test
        Starts the test runner.
    
      yarn eject
        Removes this tool and copies build dependencies, configuration files
        and scripts into the app directory. If you do this, you can’t go back!
    
    We suggest that you begin by typing:
    
      cd first_forever
      yarn start
    
    Happy hacking!
    
    

    在 package.json 文件中指明 nervos 依赖版本 "@nervos/chain": "^0.17.10",然后执行 npm install 或者 yarn install.

    package.json

    {
      "name": "first_forever",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "@nervos/web3": "^0.17.40",
        "react": "^16.4.2",
        "react-dom": "^16.4.2",
        "react-scripts": "1.1.5"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject"
      }
    }
    

    3. Add Components of the Dapp

    This step is very familiar to webapp developers, Route, Containers and Components will be added to the Dapp

    └── src
        ├── Routes.jsx
        ├── components
        └── containers
    

    The Route indicates that the demo has 4 pages:

    All above are just traditional webapp development, and next we are going to dapp development.

    4. Nervos.js

    引入 nervos.js 后,未来所有跟链交互都会通过 nervos 实例完成,我们建议在一个单独的文件中完成所有 nervos 相关的配置,示例代码如下:

    const { default: Nervos } = require('@nervos/chain')
    
    const config = require('./config')
    
    const nervos = Nervos(config.chain) // config.chain indicates that the address of Appchain to interact
    const account = nervos.eth.accounts.privateKeyToAccount(config.privateKey) // create account by private key from config
    
    nervos.eth.accounts.wallet.add(account) // add account to nervos
    
    module.exports = nervos
    

    这里需要说明一点,为什么需要检测浏览器环境、window 中是否有 nervos 实例。主要是为了实现钱包拦截 DApp 发送的交易请求,通常钱包会往浏览器环境中注入 js 代码,同时在 window 中提供 nervos 实例,而改用 window.nervos.currentProvider 初始化 Nervos 是为了方便钱包做请求拦截。

    5.配置 manifest.json

    由于 AppChain DApp 支持多链,所以需要 DApp 开发者提供一个配置文件 manifest.json, 该配置文件会包含 DApp 运行在哪些链上,以下是 manifest.json 的 示例:

    {
        "name": "Nervos First App",                                 // DApp 名称
        "blockViewer": "https://etherscan.io/",                     // 相应区块链浏览器的地址
        "chainSet": {                                               // DApp 所在链的信息集合
          "1": "http://121.196.200.225:1337"                        // key: chainId  value: 节点 host
        },
        "icon": "http://7xq40y.com1.z0.glb.clouddn.com/23.pic.jpg", // DApp 图标
        "entry": "index.html",                                      // DApp 入口地址
        "provider": "https://cryptape.com/"                         // DApp 提供者
    }
    

    从 manifest.json 文件可以看出,我们是通过 chainSet 的方式来展示多链信息,我们建议将 mainfest.json 文件放到根目录,然后在 html 文件中声明 mainfest.json 的文件路径,示例如下:

    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    

    钱包 Neuron 会读取 DApp 的 mainfest.json 文件,作为识别 DApp 发送到哪条 AppChain 的依据,简单来说,AppChain 的每一笔交易数据中都会包含 chainId ,Neuon 根据 chainId 决定发送到那个节点 IP 地址。

    6、智能合约

    AppChain 的 EVM 是完全兼容以太坊的,所以只要是能在以太坊跑通的智能合约都可以直接移植到 AppChain,目前以太坊上主流的智能合约开发语言是 Solidity,下面我们以一个简单存储 SimpleStore 合约为例,介绍一下智能合约从编写、部署 到调用的详细过程。
    Solidity 合约文件如下:

    pragma solidity 0.4.24;
    
    contract SimpleStore {
        mapping (address => mapping (uint256 => string)) private records;
        mapping (address => uint256[]) private categories;
    
        event Recorded(address _sender, string indexed _text, uint256 indexed _time);
    
        function _addToList(address from, uint256 time) private {
            categories[from].push(time);
        }
    
        function getList()
        public
        view
        returns (uint256[])
        {
            return categories[msg.sender];
        }
    
        function add(string text, uint256 time) public {
            records[msg.sender][time]=text;
            _addToList(msg.sender, time);
            emit Recorded(msg.sender, text, time);
        }
        function get(uint256 time) public view returns(string) {
    
            return records[msg.sender][time];
        }
    }
    

    这个合约逻辑很简单,主要包含了三个对外方法,一个 add 方法,用来往链上存储文字和时间戳信息,一个 get 方法,用来根据时间戳信息获取链上文字信息,最有一个是 getList 方法,用来获取当前地址下的所有历史存储信息。 合约文件相当于在 AppChain 上建立自己的服务,业务逻辑和方法完全自定义,SimpleStore 相当于在 AppChain 建立一个数据库,并且对外提供了三个可用的方法。

    由于兼容以太坊的 EVM ,故而合约编写、编译和调试可以直接使用 remix

    image.png image.png

    这里的 bytecodeabi 接下来会用到,我们姑且将其放入 compiled.js 文件中。

    $ truffle unbox Cryptape/AppChain-Truffle-Box
    
    Downloading...
    Unpacking...
    Setting up...
    Unbox successful. Sweet!
    
    Commands:
    
      Compile contracts:             truffle compile
      Migrate contracts to AppChain: npm run migrate
      Migrate contracts to Ethereum: truffle migrate
      Test contracts:                truffle test
    
    

    下载android app
    https://github.com/cryptape/Neuron-Android/releases

    Dapp demos
    https://github.com/cryptape/dapp-demos

    另外一种方式是通过nervos.js进行合约部署

    配置appchain所在链地址和账户私钥信息

    config.js

    const config = {
      chain: 'http://121.196.200.225:1337',
      privateKey: '*********************************',
      contractAddress: '{deployed contract address}',
    }
    module.exports = config
    
    

    引入nervos.js 添加私钥至nervos中钱包中.

    const {
        default: Nervos
      } = require('@nervos/chain')
      
      const config = require('./config')
      
      const nervos = Nervos(config.chain) // config.chain indicates that the address of Appchain to interact
      const account = nervos.eth.accounts.privateKeyToAccount(config.privateKey) // create account by private key from config
      
      nervos.eth.accounts.wallet.add(account) // add account to nervos
      
      module.exports = nervos
      
    

    修改package.json 增加deploy,test:contract命令

    {
      "name": "first_forever",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "@nervos/chain": "^0.17.16",
        "@nervos/web3": "^0.17.40",
        "react": "^16.4.2",
        "react-dom": "^16.4.2",
        "react-router-dom": "^4.3.1",
        "react-scripts": "1.1.5"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject",
        "deploy": "node ./src/contracts/deploy.js",
        "test:contract": "node ./src/contracts/contracts.js"
      }
    }
    
    

    编写deploy.js 脚本

    const nervos = require('../nervos')
    const {
      abi,
      bytecode
    } = require('./compiled.js')
    
    const transaction = require('./transaction')
    let _contractAddress = ''
    
    nervos.appchain.getBlockNumber().then(current => {
      transaction.validUntilBlock = +current + 88 // update transaction.validUntilBlock
      return nervos.appchain.deploy(bytecode, transaction) // deploy contract
    }).then(res => {
      const {
        contractAddress,
        errorMessage,
      } = res
      if (errorMessage) throw new Error(errorMessage)
      console.log(`contractAddress is: ${contractAddress}`)
      _contractAddress = contractAddress
      return nervos.appchain.storeAbi(contractAddress, abi, transaction) // store abi on the chain
    }).then(res => {
      if (res.errorMessage) throw new Error(res.errorMessage)
      return nervos.appchain.getAbi(_contractAddress).then(console.log) // get abi from the chain
    }).catch(err => console.error(err))
    
    

    compile.js 文件为solidity合约在remix中编译后的信息:bytecode,abi

    const bytecode = "608060405234801561001057600080fd5b506105de806100206000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806336555b851461005c578063942b765a146100cf5780639507d39a1461013b575b600080fd5b34801561006857600080fd5b506100cd600480360381019080803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290803590602001909291905050506101e1565b005b3480156100db57600080fd5b506100e461031a565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561012757808201518184015260208101905061010c565b505050509050019250505060405180910390f35b34801561014757600080fd5b50610166600480360381019080803590602001909291905050506103af565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101a657808201518184015260208101905061018b565b50505050905090810190601f1680156101d35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020908051906020019061024492919061050d565b5061024f33826104a0565b80826040518082805190602001908083835b6020831015156102865780518252602082019150602081019050602083039250610261565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207fe4af93ca7e370881e6f1b57272e42a3d851d3cc6d951b4f4d2e7a963914468a233604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a35050565b6060600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054806020026020016040519081016040528092919081815260200182805480156103a557602002820191906000526020600020905b815481526020019060010190808311610391575b5050505050905090565b60606000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008381526020019081526020016000208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104945780601f1061046957610100808354040283529160200191610494565b820191906000526020600020905b81548152906001019060200180831161047757829003601f168201915b50505050509050919050565b600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190806001815401808255809150509060018203906000526020600020016000909192909190915055505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061054e57805160ff191683800117855561057c565b8280016001018555821561057c579182015b8281111561057b578251825591602001919060010190610560565b5b509050610589919061058d565b5090565b6105af91905b808211156105ab576000816000905550600101610593565b5090565b905600a165627a7a723058201d2b92fc42d4c6ada65ef9914cefd95a0b5876c172fbcf289ad7d86846506ae70029"
    
    const abi =   [
        {
            "constant": false,
            "inputs": [
                {
                    "name": "text",
                    "type": "string"
                },
                {
                    "name": "time",
                    "type": "uint256"
                }
            ],
            "name": "add",
            "outputs": [],
            "payable": false,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": true,
            "inputs": [],
            "name": "getList",
            "outputs": [
                {
                    "name": "",
                    "type": "uint256[]"
                }
            ],
            "payable": false,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": true,
            "inputs": [
                {
                    "name": "time",
                    "type": "uint256"
                }
            ],
            "name": "get",
            "outputs": [
                {
                    "name": "",
                    "type": "string"
                }
            ],
            "payable": false,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "anonymous": false,
            "inputs": [
                {
                    "indexed": false,
                    "name": "_sender",
                    "type": "address"
                },
                {
                    "indexed": true,
                    "name": "_text",
                    "type": "string"
                },
                {
                    "indexed": true,
                    "name": "_time",
                    "type": "uint256"
                }
            ],
            "name": "Recorded",
            "type": "event"
        }
    ]
    
    module.exports = {
      abi,
      bytecode
    }
    
    

    transaction.js 构建交易对象 ,部署合约的时候有用到 .

     const nervos = require('../nervos')
    const transaction = {
      from: nervos.eth.accounts.wallet[0].address,
      privateKey: nervos.eth.accounts.wallet[0].privateKey,
      nonce: 999999,
      quota: 1000000,
      chainId: 1,
      version: 0,
      validUntilBlock: 999999,
      value: '0x0'
    };
    
    module.exports = transaction
    
    

    使用以及部署好的合约中的方法

    const nervos = require('./nervos')
    const {
      abi
    } = require('./contracts/compiled.js')
    const {
      contractAddress
    } = require('./config')
    
    const transaction = require('./contracts/transaction')
    const simpleStoreContract = new nervos.appchain.Contract(abi, contractAddress)
    module.exports = {
      transaction,
      simpleStoreContract
    }
    
    

    相关文章

      网友评论

          本文标题:开发第一个 Nervos Dapp

          本文链接:https://www.haomeiwen.com/subject/qybqwftx.html