├── .gitignore ├── Dockerfile ├── README.md ├── bs-config.json ├── build_img.sh ├── contracts ├── .gitkeep ├── ERC721.sol ├── Migrations.sol ├── Ownable.sol ├── SafeMath.sol └── ThingCore.sol ├── img └── 1.png ├── importAccount.js ├── lazy_push.sh ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── package-lock.json ├── package.json ├── private-eth ├── account │ ├── account.txt │ ├── k1.txt │ ├── k2.txt │ ├── k3.txt │ ├── k4.txt │ ├── k5.txt │ ├── k6.txt │ ├── k7.txt │ ├── k8.txt │ └── pw.txt ├── genesis.json └── run.sh ├── run.sh ├── src ├── app.js ├── config.json ├── css │ ├── bootstrap.min.css │ └── bootstrap.min.css.map ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── images │ ├── 0.svg │ ├── 1.svg │ ├── 10.svg │ ├── 11.svg │ ├── 12.svg │ ├── 13.svg │ ├── 14.svg │ ├── 15.svg │ ├── 16.svg │ ├── 17.svg │ ├── 18.svg │ ├── 19.svg │ ├── 2.svg │ ├── 20.svg │ ├── 21.svg │ ├── 22.svg │ ├── 23.svg │ ├── 24.svg │ ├── 25.svg │ ├── 26.svg │ ├── 27.svg │ ├── 28.svg │ ├── 29.svg │ ├── 3.svg │ ├── 30.svg │ ├── 31.svg │ ├── 32.svg │ ├── 33.svg │ ├── 34.svg │ ├── 35.svg │ ├── 36.svg │ ├── 37.svg │ ├── 38.svg │ ├── 39.svg │ ├── 4.svg │ ├── 40.svg │ ├── 41.svg │ ├── 42.svg │ ├── 43.svg │ ├── 44.svg │ ├── 45.svg │ ├── 46.svg │ ├── 47.svg │ ├── 48.svg │ ├── 49.svg │ ├── 5.svg │ ├── 50.svg │ ├── 51.svg │ ├── 52.svg │ ├── 53.svg │ ├── 54.svg │ ├── 55.svg │ ├── 56.svg │ ├── 57.svg │ ├── 58.svg │ ├── 59.svg │ ├── 6.svg │ ├── 60.svg │ ├── 61.svg │ ├── 62.svg │ ├── 63.svg │ ├── 64.svg │ ├── 65.svg │ ├── 66.svg │ ├── 67.svg │ ├── 68.svg │ ├── 69.svg │ ├── 7.svg │ ├── 70.svg │ ├── 71.svg │ ├── 72.svg │ ├── 73.svg │ ├── 74.svg │ ├── 75.svg │ ├── 76.svg │ ├── 77.svg │ ├── 78.svg │ ├── 79.svg │ ├── 8.svg │ ├── 80.svg │ ├── 81.svg │ ├── 82.svg │ ├── 83.svg │ ├── 84.svg │ ├── 85.svg │ ├── 86.svg │ ├── 87.svg │ ├── 88.svg │ ├── 89.svg │ ├── 9.svg │ ├── 90.svg │ ├── 91.svg │ ├── 92.svg │ ├── 93.svg │ ├── 94.svg │ ├── 95.svg │ ├── 96.svg │ ├── 97.svg │ ├── 98.svg │ └── 99.svg ├── index.html ├── js │ ├── bootstrap.min.js │ ├── jquery.min.js │ ├── truffle-contract.js │ └── web3.min.js └── server.js └── truffle.js /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX leaves these everywhere on SMB shares 2 | ._* 3 | 4 | # OSX trash 5 | .DS_Store 6 | 7 | # Eclipse files 8 | .classpath 9 | .project 10 | .settings/** 11 | 12 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 13 | .idea/ 14 | *.iml 15 | 16 | node_modules 17 | 18 | build -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:latest 2 | 3 | RUN apk update && apk add bash vim tzdata geth \ 4 | && cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 5 | 6 | ADD . /CryptoKitties 7 | WORKDIR /CryptoKitties 8 | 9 | RUN npm install 10 | 11 | EXPOSE 3000 8545 12 | CMD ["sh", "run.sh"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CryptoKitties 2 | ========================= 3 | 4 | A simple implementation of CryptoKitties with more tricks. 5 | 6 | More Info:[Ethereum-ERC721智能合约和Dapp实践--以太猫CryptoKitties的简单实现](https://hello2mao.github.io/2018/07/27/ethereum-erc721-demo/) 7 | 8 | ![image](/img/1.png) 9 | 10 | # Feature 11 | 12 | * 交易系统:用户可使用帐号在商店里对产品进行买卖交易。 13 | * 繁育系统:用户可以使用已有的产品与繁殖中心的产品进行繁殖,产生新的产品。 14 | * 对战系统:用户可以使用已有的产品与对战心中的产品进行对战,赢了将升级并产生一个新产品,输了失败次数将加一。 15 | * 喂养系统:用户可以对已有的产品喂养以太坊公链上的以太猫,从而产生新的带以太猫基因的杂交品种。 16 | * 升级系统:用户可以对已有的产品花ETH进行升级,2级以后可以改名,20级后可以定制DNA,从而用户激励升级。 17 | 18 | # Quick Start 19 | 20 | ```shell 21 | # run dapp 22 | docker run --name=dapp -p 3000:3000 -p 8545:8545 -d hello2mao/crypto-kitties:v1.0.0 23 | # show logs 24 | docker logs -f dapp 25 | ``` 26 | 27 | Wait some minutes. 28 | Visit [http://localhost:3000](http://localhost:3000) 29 | 30 | # License 31 | 32 | 33 | Copyright (C) 2018 hello2mao. 34 | 35 | Licensed under the Apache License, Version 2.0 (the "License"); 36 | you may not use this file except in compliance with the License. 37 | You may obtain a copy of the License at 38 | 39 | http://www.apache.org/licenses/LICENSE-2.0 40 | 41 | Unless required by applicable law or agreed to in writing, software 42 | distributed under the License is distributed on an "AS IS" BASIS, 43 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | See the License for the specific language governing permissions and 45 | limitations under the License. 46 | -------------------------------------------------------------------------------- /bs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "baseDir": ["./src", "./build/contracts"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /build_img.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | VERSION=v1.0.0 3 | 4 | docker rmi -f hello2mao/crypto-kitties:${VERSION} 5 | docker build -t hello2mao/crypto-kitties:${VERSION} . 6 | docker push hello2mao/crypto-kitties:${VERSION} -------------------------------------------------------------------------------- /contracts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello2mao/CryptoKitties/2c08114154442eacc71fb59af89ee78f4692304d/contracts/.gitkeep -------------------------------------------------------------------------------- /contracts/ERC721.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.19; 2 | 3 | contract ERC721 { 4 | 5 | event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); 6 | event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); 7 | 8 | function balanceOf(address _owner) public view returns (uint256 _balance); 9 | function ownerOf(uint256 _tokenId) public view returns (address _owner); 10 | function transfer(address _to, uint256 _tokenId) public; 11 | function approve(address _to, uint256 _tokenId) public; 12 | function takeOwnership(uint256 _tokenId) public; 13 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.2; 2 | 3 | contract Migrations { 4 | 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | if (msg.sender == owner) _; 10 | } 11 | 12 | function Migrations() public { 13 | owner = msg.sender; 14 | } 15 | 16 | function setCompleted(uint completed) restricted public { 17 | last_completed_migration = completed; 18 | } 19 | 20 | function upgrade(address new_address) restricted public { 21 | Migrations upgraded = Migrations(new_address); 22 | upgraded.setCompleted(last_completed_migration); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.19; 2 | 3 | contract Ownable { 4 | 5 | address public owner; 6 | 7 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 8 | 9 | function Ownable() public { 10 | owner = msg.sender; 11 | } 12 | 13 | modifier onlyOwner() { 14 | require(msg.sender == owner); 15 | _; 16 | } 17 | 18 | function transferOwnership(address newOwner) public onlyOwner { 19 | require(newOwner != address(0)); 20 | OwnershipTransferred(owner, newOwner); 21 | owner = newOwner; 22 | } 23 | } -------------------------------------------------------------------------------- /contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.19; 2 | 3 | library SafeMath { 4 | 5 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 6 | if (a == 0) { 7 | return 0; 8 | } 9 | uint256 c = a * b; 10 | assert(c / a == b); 11 | return c; 12 | } 13 | 14 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 15 | // assert(b > 0); // Solidity automatically throws when dividing by 0 16 | uint256 c = a / b; 17 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 18 | return c; 19 | } 20 | 21 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 22 | assert(b <= a); 23 | return a - b; 24 | } 25 | 26 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 27 | uint256 c = a + b; 28 | assert(c >= a); 29 | return c; 30 | } 31 | } -------------------------------------------------------------------------------- /contracts/ThingCore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.19; 2 | 3 | import './ERC721.sol'; 4 | import './SafeMath.sol'; 5 | import './Ownable.sol'; 6 | 7 | /// 产品生产者 8 | contract ThingFactory is Ownable { 9 | 10 | using SafeMath for uint256; 11 | 12 | // 生产一个产品后的通知事件 13 | event NewThing(address indexed _from, uint thingId, string name, uint dna); 14 | // 日志事件 15 | event LogStatus(address indexed _from, string log); 16 | 17 | // 基因位数 18 | uint dnaDigits = 16; 19 | uint dnaModulus = 10 ** dnaDigits; 20 | // 技能冷却时间 21 | uint cooldownTime = 1 days; 22 | 23 | struct Thing { 24 | string name; // 名字 25 | uint price; // 价格 26 | uint dna; // DNA 27 | uint32 level; // 等级 28 | uint32 readyTime; // 技能冷却 29 | uint32 generation; // 代数 30 | uint16 winCount; // 战斗胜利次数 31 | uint16 lossCount; // 战斗失败次数 32 | } 33 | 34 | Thing[] public things; 35 | 36 | // _tokenId <==> _owner 37 | mapping (uint => address) public thingToOwner; 38 | // _owner <==> _tokenCount 39 | mapping (address => uint) ownerThingCount; 40 | 41 | function _createThing(string _name, uint _dna, uint32 _generation) internal { 42 | require(msg.sender != address(0)); 43 | 44 | // 配置默认产品 45 | Thing memory _thing; 46 | _thing.name = _name; 47 | _thing.price = (_dna / 100) % 100; 48 | _thing.dna = _dna; 49 | _thing.level = uint32(1); 50 | _thing.readyTime = uint32(now); 51 | _thing.generation = _generation; 52 | _thing.winCount = uint16(0); 53 | _thing.lossCount = uint16(0); 54 | 55 | // 记录到区块链 56 | uint id = things.push(_thing) - 1; 57 | thingToOwner[id] = msg.sender; 58 | ownerThingCount[msg.sender]++; 59 | 60 | // 通知事件 61 | NewThing(msg.sender, id, _name, _dna); 62 | } 63 | 64 | function _generateRandomDna(string _str) internal view returns (uint) { 65 | uint rand = uint(keccak256(_str)); 66 | return rand % dnaModulus; 67 | } 68 | 69 | // 对外接口,用于生产产品 70 | function createRandomThing(string _name, uint _limit) public { 71 | require(ownerThingCount[msg.sender] <= _limit); 72 | uint randDna = _generateRandomDna(_name); 73 | randDna = randDna - randDna % 100; 74 | _createThing(_name, randDna, uint32(0)); 75 | } 76 | 77 | modifier onlyOwnerOf(uint _thingId) { 78 | require(msg.sender == thingToOwner[_thingId]); 79 | _; 80 | } 81 | 82 | function withdraw() external onlyOwner { 83 | owner.transfer(this.balance); 84 | } 85 | 86 | // 对外接口,返回相应owner的产品Id数组 87 | function getThingsByOwner(address _owner) external view returns(uint[]) { 88 | uint[] memory result = new uint[](ownerThingCount[_owner]); 89 | uint counter = 0; 90 | for (uint i = 0; i < things.length; i++) { 91 | if (thingToOwner[i] == _owner) { 92 | result[counter] = i; 93 | counter++; 94 | } 95 | } 96 | return result; 97 | } 98 | 99 | // 对外接口,返回对应Id的产品 100 | function getThing(uint _thingId) public view returns ( 101 | string name, 102 | uint price, 103 | uint dna, 104 | uint32 level, 105 | uint32 readyTime, 106 | uint32 generation, 107 | uint16 winCount, 108 | uint16 lossCount) { 109 | Thing storage thing = things[_thingId]; 110 | name = thing.name; 111 | price = thing.price; 112 | dna = thing.dna; 113 | level = thing.level; 114 | readyTime = thing.readyTime; 115 | generation = thing.generation; 116 | winCount = thing.winCount; 117 | lossCount = thing.lossCount; 118 | } 119 | 120 | function _triggerCooldown(Thing storage _thing) internal { 121 | _thing.readyTime = uint32(now + cooldownTime); 122 | } 123 | 124 | function _isReady(Thing storage _thing) internal view returns (bool) { 125 | return (_thing.readyTime <= now); 126 | } 127 | 128 | function strConcat(string _a, string _b, string _c, string _d, string _e) internal pure returns (string){ 129 | bytes memory _ba = bytes(_a); 130 | bytes memory _bb = bytes(_b); 131 | bytes memory _bc = bytes(_c); 132 | bytes memory _bd = bytes(_d); 133 | bytes memory _be = bytes(_e); 134 | string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length); 135 | bytes memory babcde = bytes(abcde); 136 | uint k = 0; 137 | for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i]; 138 | for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i]; 139 | for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i]; 140 | for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i]; 141 | for (i = 0; i < _be.length; i++) babcde[k++] = _be[i]; 142 | return string(babcde); 143 | } 144 | } 145 | 146 | /// 繁育&喂养系统 147 | contract ThingFeedAndBreed is ThingFactory { 148 | 149 | function feedAndMultiply(uint _thingId, uint _targetDna, string _species) internal onlyOwnerOf(_thingId) { 150 | Thing storage myThing = things[_thingId]; 151 | require(_isReady(myThing)); 152 | _targetDna = _targetDna % dnaModulus; 153 | uint newDna = (myThing.dna + _targetDna) / 2; 154 | // 吃了Kitty后,dna最后两个数字设定为99 155 | // 例如:7290459416715799 156 | if (keccak256(_species) == keccak256("kitty")) { 157 | newDna = newDna - newDna % 100 + 99; 158 | } 159 | _createThing(strConcat("n",myThing.name, "", "", ""), newDna, myThing.generation + 1); 160 | _triggerCooldown(myThing); 161 | } 162 | 163 | // 喂养 164 | function feedOnKitty(uint _thingId, uint _kittyId) public { 165 | // 使用_kittyId作为kittyDna 166 | uint kittyDna = _kittyId; 167 | // (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); 168 | feedAndMultiply(_thingId, kittyDna, "kitty"); 169 | } 170 | 171 | // 繁育 172 | function breed(uint _thingId, uint _targetThingId) public { 173 | uint _targetDna = things[_targetThingId].dna; 174 | feedAndMultiply(_thingId, _targetDna, "thing"); 175 | } 176 | } 177 | 178 | /// 升级系统 179 | contract ThingUpgrade is ThingFactory { 180 | 181 | // 默认升级费用 1 ETH 182 | uint levelUpFee = 1 ether; 183 | 184 | modifier aboveLevel(uint _level, uint _thingId) { 185 | require(things[_thingId].level >= _level); 186 | _; 187 | } 188 | 189 | // 设置升级费用 190 | function setLevelUpFee(uint _fee) external onlyOwner { 191 | levelUpFee = _fee; 192 | } 193 | 194 | // 升级 195 | function levelUp(uint _thingId) external payable { 196 | require(msg.value == levelUpFee); 197 | things[_thingId].level++; 198 | } 199 | 200 | // 2级可以改名 201 | function changeName(uint _thingId, string _newName) external aboveLevel(2, _thingId) onlyOwnerOf(_thingId) { 202 | things[_thingId].name = _newName; 203 | } 204 | 205 | // 20级可以定制Dna 206 | function changeDna(uint _thingId, uint _newDna) external aboveLevel(20, _thingId) onlyOwnerOf(_thingId) { 207 | things[_thingId].dna = _newDna; 208 | } 209 | } 210 | 211 | /// 对战系统 212 | contract ThingAttack is ThingFactory, ThingFeedAndBreed { 213 | 214 | uint randNonce = 0; 215 | // 攻击方将有70%的几率获胜,防守方将有30%的几率获胜 216 | uint attackVictoryProbability = 70; 217 | 218 | function randMod(uint _modulus) internal returns(uint) { 219 | randNonce++; 220 | return uint(keccak256(now, msg.sender, randNonce)) % _modulus; 221 | } 222 | 223 | // 两个产品互相对战 224 | function attack(uint _thingId, uint _targetId) external onlyOwnerOf(_thingId) { 225 | Thing storage myThing = things[_thingId]; 226 | Thing storage enemyThing = things[_targetId]; 227 | uint rand = randMod(100); 228 | if (rand <= attackVictoryProbability) { 229 | myThing.winCount++; 230 | myThing.level++; 231 | enemyThing.lossCount++; 232 | feedAndMultiply(_thingId, enemyThing.dna, "thing"); 233 | } else { 234 | myThing.lossCount++; 235 | enemyThing.winCount++; 236 | _triggerCooldown(myThing); 237 | } 238 | } 239 | } 240 | 241 | /// ERC721 Impl 242 | contract ThingCore is ThingAttack, ThingUpgrade, ERC721 { 243 | 244 | using SafeMath for uint256; 245 | 246 | mapping (uint => address) thingApprovals; 247 | 248 | // ERC721 impl 249 | function balanceOf(address _owner) public view returns (uint256 _balance) { 250 | return ownerThingCount[_owner]; 251 | } 252 | 253 | // ERC721 impl 254 | function ownerOf(uint256 _tokenId) public view returns (address _owner) { 255 | return thingToOwner[_tokenId]; 256 | } 257 | 258 | function _transfer(address _from, address _to, uint256 _tokenId) private { 259 | ownerThingCount[_to] = ownerThingCount[_to].add(1); 260 | ownerThingCount[_from] = ownerThingCount[_from].sub(1); 261 | thingToOwner[_tokenId] = _to; 262 | Transfer(_from, _to, _tokenId); 263 | } 264 | 265 | // ERC721 impl 266 | function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { 267 | _transfer(msg.sender, _to, _tokenId); 268 | } 269 | 270 | // ERC721 impl 271 | function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { 272 | thingApprovals[_tokenId] = _to; 273 | Approval(msg.sender, _to, _tokenId); 274 | } 275 | 276 | // ERC721 impl 277 | function takeOwnership(uint256 _tokenId) public { 278 | require(thingApprovals[_tokenId] == msg.sender); 279 | address owner = ownerOf(_tokenId); 280 | _transfer(owner, msg.sender, _tokenId); 281 | } 282 | 283 | function buyThing(uint _thingId) public payable { 284 | address owner = thingToOwner[_thingId]; 285 | _transfer(owner, msg.sender, _thingId); 286 | } 287 | } -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello2mao/CryptoKitties/2c08114154442eacc71fb59af89ee78f4692304d/img/1.png -------------------------------------------------------------------------------- /importAccount.js: -------------------------------------------------------------------------------- 1 | var accountPrivKeys = [ 2 | '6aa931d33e1b2f0711ec8531ee9f856809137c47c736853070b7042609e27ec7', 3 | 'd7bde311a5275c0772da231144201053cc180abb5f9d4d2276d464b70e874ae7', 4 | '1d1671b5d9e4c7ec691ed5211c08319e8ed20f187224cc1f783eb5b441535fa6', 5 | 'db985da6fefb81b12c36997b991a1e3b44b66b1fcf615fe9e003fa5d3b11cc78', 6 | '0ee3f0ac2abc00453fd11ae25e60b82e0129392d5ad7de4ea36d1011559bb639', 7 | '619d316088b697c85ad1d9331ae01d9acbddb318cc704fc40de62ff17bc2e3da', 8 | 'ab900185b94700488362b6fbb87406f6df66f69524cbaa3b554179d3c2896b93' 9 | ]; 10 | 11 | for (index in accountPrivKeys) { 12 | console.log('process importRawKey: ' + accountPrivKeys[index]); 13 | try { 14 | web3.personal.importRawKey(accountPrivKeys[index], '123'); 15 | } 16 | catch (e) { 17 | console.log('ignore err:', e); 18 | } 19 | } 20 | var accounts = eth.accounts; 21 | for (index in accounts) { 22 | try { 23 | console.log('unlock account: ' + accounts[index]); 24 | personal.unlockAccount(accounts[index], '123', 999999999); 25 | } 26 | catch (e) { 27 | console.log('ignore err:', e); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lazy_push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git add . 3 | date=`date` 4 | git commit -m "Lazy push: $date" 5 | git push 6 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var ThingCore = artifacts.require("ThingCore"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(ThingCore); 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CryptoKitties", 3 | "version": "1.0.0", 4 | "description": "A simple implementation of CryptoKitties with more tricks.", 5 | "main": "truffle.js", 6 | "scripts": { 7 | "all": "./node_modules/.bin/truffle compile --compile-all && ./node_modules/.bin/truffle migrate --reset && ./node_modules/.bin/lite-server -c bs-config.json", 8 | "deploy": "./node_modules/.bin/truffle compile --compile-all && ./node_modules/.bin/truffle migrate --reset", 9 | "dev": "./node_modules/.bin/lite-server -c bs-config.json " 10 | }, 11 | "author": "hello2mao@gmail.com", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "express": "^4.16.3", 15 | "lite-server": "^2.3.0", 16 | "truffle": "^4.1.3" 17 | }, 18 | "dependencies": {}, 19 | "resolutions": { 20 | "**/**/fsevents": "^1.2.9" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /private-eth/account/account.txt: -------------------------------------------------------------------------------- 1 | 0x3A834e8c4ab7CfF61C11F71129205020bd828533 2 | a8bfe41bb713d77615e8a191af5b502b855f8edb74a049e155ab6a09cf310c43 3 | 4 | 0x673751313d2E477F789F0a1f0A4Fc4208ba908b3 5 | 4e5cc52423dafa8db0036513342b4f365ffc2036bddd3093d9a5f068dd2ebeef 6 | 7 | 0xcf7EC987043f1314F2BB7ba8963fcEA2e15a1b18 8 | cc77f8677108a3beeed0c418cf63d8801d51b9553a710093dbbb4ce52a2062c5 9 | 10 | 0x44fE4bce1867C7bA318aA6A7bEB1d0795db3cA62 11 | 5ea2a52ca1b4d42410052f33b492fae680101fb909953ca72b5b8a663d62e147 12 | 13 | 0xe9B1ADcFfF8cAbA1371C10210FFB49A91058d7b9 14 | 68537ea3937dfc9a582090d63d8175097e011ea11a089c53e829ac81434e6141 15 | 16 | 0xa7383b6C584f10f3e7f870AcB3495C9B907264f1 17 | 24c02e773d5ab1f4c086f6ee40d7309d4ba0f951c4537a6c4aa8fcc2d68a7927 18 | 19 | 0xEc0D61b303C2ECEd7d18B166D70f7C7DC69c379B 20 | 3aef08bd34d0e4b7cd16fdc0aeb69b45225ccdb852632ae7f9f547e464055a7f 21 | 22 | 0xFCed13a4232257B371c910293A840EAC1863FF0A 23 | 065f3c9e5005fca9c44f3563d43aafc1f095d27055ca86b7d953d29045b764e6 -------------------------------------------------------------------------------- /private-eth/account/k1.txt: -------------------------------------------------------------------------------- 1 | a8bfe41bb713d77615e8a191af5b502b855f8edb74a049e155ab6a09cf310c43 -------------------------------------------------------------------------------- /private-eth/account/k2.txt: -------------------------------------------------------------------------------- 1 | 4e5cc52423dafa8db0036513342b4f365ffc2036bddd3093d9a5f068dd2ebeef -------------------------------------------------------------------------------- /private-eth/account/k3.txt: -------------------------------------------------------------------------------- 1 | cc77f8677108a3beeed0c418cf63d8801d51b9553a710093dbbb4ce52a2062c5 -------------------------------------------------------------------------------- /private-eth/account/k4.txt: -------------------------------------------------------------------------------- 1 | 5ea2a52ca1b4d42410052f33b492fae680101fb909953ca72b5b8a663d62e147 -------------------------------------------------------------------------------- /private-eth/account/k5.txt: -------------------------------------------------------------------------------- 1 | 68537ea3937dfc9a582090d63d8175097e011ea11a089c53e829ac81434e6141 -------------------------------------------------------------------------------- /private-eth/account/k6.txt: -------------------------------------------------------------------------------- 1 | 24c02e773d5ab1f4c086f6ee40d7309d4ba0f951c4537a6c4aa8fcc2d68a7927 -------------------------------------------------------------------------------- /private-eth/account/k7.txt: -------------------------------------------------------------------------------- 1 | 3aef08bd34d0e4b7cd16fdc0aeb69b45225ccdb852632ae7f9f547e464055a7f -------------------------------------------------------------------------------- /private-eth/account/k8.txt: -------------------------------------------------------------------------------- 1 | 065f3c9e5005fca9c44f3563d43aafc1f095d27055ca86b7d953d29045b764e6 -------------------------------------------------------------------------------- /private-eth/account/pw.txt: -------------------------------------------------------------------------------- 1 | 123 -------------------------------------------------------------------------------- /private-eth/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "chainId": 15, 4 | "homesteadBlock": 0, 5 | "eip150Block": 0, 6 | "eip155Block": 0, 7 | "eip158Block": 0, 8 | "byzantiumBlock": 0, 9 | "constantinopleBlock": 0, 10 | "petersburgBlock": 0, 11 | "clique": { 12 | "period": 5, 13 | "epoch": 30000 14 | } 15 | }, 16 | "difficulty": "1", 17 | "gasLimit": "0xffffffffffff", 18 | "extradata": "0x00000000000000000000000000000000000000000000000000000000000000003A834e8c4ab7CfF61C11F71129205020bd8285330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 19 | "alloc": { 20 | "3A834e8c4ab7CfF61C11F71129205020bd828533": { "balance": "0xc097ce7bc90715b34b9f1" }, 21 | "673751313d2E477F789F0a1f0A4Fc4208ba908b3": { "balance": "0xc097ce7bc90715b34b9f1" }, 22 | "cf7EC987043f1314F2BB7ba8963fcEA2e15a1b18": { "balance": "0xc097ce7bc90715b34b9f1" }, 23 | "44fE4bce1867C7bA318aA6A7bEB1d0795db3cA62": { "balance": "0xc097ce7bc90715b34b9f1" }, 24 | "e9B1ADcFfF8cAbA1371C10210FFB49A91058d7b9": { "balance": "0xc097ce7bc90715b34b9f1" }, 25 | "a7383b6C584f10f3e7f870AcB3495C9B907264f1": { "balance": "0xc097ce7bc90715b34b9f1" }, 26 | "Ec0D61b303C2ECEd7d18B166D70f7C7DC69c379B": { "balance": "0xc097ce7bc90715b34b9f1" }, 27 | "FCed13a4232257B371c910293A840EAC1863FF0A": { "balance": "0xc097ce7bc90715b34b9f1" } 28 | } 29 | } -------------------------------------------------------------------------------- /private-eth/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # clear data 4 | rm -rf data 5 | mkdir data 6 | 7 | # init genesis 8 | geth init --datadir data genesis.json >> geth.log 2>&1 9 | 10 | # import account 11 | geth --datadir data account import account/k1.txt --password account/pw.txt >> geth.log 2>&1 12 | geth --datadir data account import account/k2.txt --password account/pw.txt >> geth.log 2>&1 13 | geth --datadir data account import account/k3.txt --password account/pw.txt >> geth.log 2>&1 14 | geth --datadir data account import account/k4.txt --password account/pw.txt >> geth.log 2>&1 15 | geth --datadir data account import account/k5.txt --password account/pw.txt >> geth.log 2>&1 16 | geth --datadir data account import account/k6.txt --password account/pw.txt >> geth.log 2>&1 17 | geth --datadir data account import account/k7.txt --password account/pw.txt >> geth.log 2>&1 18 | geth --datadir data account import account/k8.txt --password account/pw.txt >> geth.log 2>&1 19 | 20 | # start geth 21 | geth --datadir data --networkid 15 --mine --nodiscover --rpc --rpcport 8545 --rpccorsdomain "*" --rpcapi "admin,clique,debug,miner,net,rpc,eth,txpool,web3,personal" --rpcaddr 0.0.0.0 --unlock "0x3A834e8c4ab7CfF61C11F71129205020bd828533,0x673751313d2E477F789F0a1f0A4Fc4208ba908b3,0xcf7EC987043f1314F2BB7ba8963fcEA2e15a1b18,0x44fE4bce1867C7bA318aA6A7bEB1d0795db3cA62,0xe9B1ADcFfF8cAbA1371C10210FFB49A91058d7b9,0xa7383b6C584f10f3e7f870AcB3495C9B907264f1,0xEc0D61b303C2ECEd7d18B166D70f7C7DC69c379B,0xFCed13a4232257B371c910293A840EAC1863FF0A" --password account/pw.txt >> geth.log 2>&1 & 22 | 23 | echo "run success." 24 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd private-eth && sh run.sh 4 | 5 | echo "start to run DApp..." 6 | sleep 5s 7 | cd .. && npm run all 8 | echo "run DApp success" -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | App = { 2 | web3Provider: null, 3 | contracts: {}, 4 | api: null, 5 | tabs: ['TradeCenter', 'BreedCenter', 'FightCenter', 'FeedCenter', 'UpgradeCenter', 'Me'], 6 | currentTab: null, 7 | config: {}, 8 | currentAccount: null, 9 | currentAccountBalance: 0, 10 | 11 | init: function () { 12 | // 初始化配置 13 | $.getJSON('./config.json', function (data) { 14 | // read from config.json 15 | App.config.debug = data.debug; 16 | App.config.dappName = data.dapp_name; 17 | App.config.rpc = data.rpc; 18 | App.config.networkId = data.network_id; 19 | App.config.defaultTradeCenterThingsNum = data.default_trade_center_things_num; 20 | App.config.defaultBreedCenterThingsNum = data.default_breed_center_things_num; 21 | App.config.defaultFightCenterThingsNum = data.default_fight_center_things_num; 22 | App.config.defaultUsersThingsNum = data.default_users_things_num; 23 | App.config.defaultTradeCenterAccount = data.default_accounts.trade_center; 24 | App.config.defaultBreedCenterAccount = data.default_accounts.breed_center; 25 | App.config.defaultFightCenterAccount = data.default_accounts.fight_center; 26 | App.config.defaultFeedCenterAccount = data.default_accounts.feed_center; 27 | App.config.defaultUpgradeCenterAccount = data.default_accounts.upgrade_center; 28 | App.config.defaultUsersAccount = data.default_accounts.users; 29 | App.config.levelUpFee = data.level_up_fee; 30 | 31 | // init global var 32 | App.currentAccount = data.default_accounts.users[0]; 33 | $('#current-account').text(App.currentAccount); 34 | App.currentTab = App.tabs[0]; 35 | }).then(function () { 36 | App.initWeb3(); 37 | }); 38 | }, 39 | 40 | // 初始化Web3 41 | initWeb3: function () { 42 | App.web3Provider = new Web3.providers.HttpProvider(App.config.rpc); 43 | web3 = new Web3(App.web3Provider); 44 | return App.initContract(); 45 | }, 46 | 47 | // 初始化合约相关 48 | initContract: function () { 49 | $.getJSON('ThingCore.json', function (data) { 50 | if (App.config.debug) { 51 | // console.log('Contract abi: ' + JSON.stringify(data.abi)); 52 | console.log('Contract networks: ' + JSON.stringify(data.networks)); 53 | } 54 | 55 | // Get the necessary contract artifact file and instantiate it with truffle-contract 56 | App.contracts.ThingCore = TruffleContract(data); 57 | // Set network id 58 | App.contracts.ThingCore.setNetwork(App.config.networkId); 59 | // Set the provider for our contract 60 | App.contracts.ThingCore.setProvider(App.web3Provider); 61 | App.api = web3.eth.contract(App.contracts.ThingCore.abi).at(App.contracts.ThingCore.address); 62 | // Log Event 63 | App.api.LogStatus().watch(function (error, result) { 64 | if (!error) { 65 | console.log('LogStatus: ' + JSON.stringify(result.args.log)); 66 | } 67 | else { 68 | console.log('LogStatus error: ' + error.message); 69 | } 70 | }); 71 | // ThingFactory的NewThing事件 72 | App.api.NewThing().watch(function (error, result) { 73 | if (!error) { 74 | console.log('NewThing: ' + JSON.stringify(result)); 75 | switch (result.args._from.toLowerCase()) { 76 | case App.config.defaultTradeCenterAccount.toLowerCase(): 77 | App.loadThing(result.args.thingId, App.tabs[0]); 78 | break; 79 | case App.config.defaultBreedCenterAccount.toLowerCase(): 80 | App.loadThing(result.args.thingId, App.tabs[1]); 81 | break; 82 | case App.config.defaultFightCenterAccount.toLowerCase(): 83 | App.loadThing(result.args.thingId, App.tabs[2]); 84 | break; 85 | default: 86 | App.loadThing(result.args.thingId, App.tabs[5]); 87 | break; 88 | } 89 | } 90 | else { 91 | console.log('NewThing error: ' + error.message); 92 | } 93 | }); 94 | // ERC721 Transfer事件 95 | App.api.Transfer().watch(function (error, result) { 96 | if (!error) { 97 | console.log('Transfer: ' + JSON.stringify(result)); 98 | App.updateBalance(); 99 | switch (result.args._from.toLowerCase()) { 100 | case App.config.defaultTradeCenterAccount.toLowerCase(): 101 | App.removeThing(result.args._tokenId, App.tabs[0]); 102 | break; 103 | case App.config.defaultBreedCenterAccount.toLowerCase(): 104 | App.removeThing(result.args._tokenId, App.tabs[1]); 105 | break; 106 | case App.config.defaultFightCenterAccount.toLowerCase(): 107 | App.removeThing(result.args._tokenId, App.tabs[2]); 108 | break; 109 | default: 110 | console.log('Transfer _from default'); 111 | App.removeThing(result.args._tokenId, App.tabs[5]); 112 | break; 113 | } 114 | switch (result.args._to.toLowerCase()) { 115 | case App.config.defaultTradeCenterAccount.toLowerCase(): 116 | App.loadThing(result.args.thingId, App.tabs[0]); 117 | break; 118 | case App.config.defaultBreedCenterAccount.toLowerCase(): 119 | App.loadThing(result.args.thingId, App.tabs[1]); 120 | break; 121 | case App.config.defaultFightCenterAccount.toLowerCase(): 122 | App.loadThing(result.args.thingId, App.tabs[2]); 123 | break; 124 | default: 125 | console.log('Transfer _to default'); 126 | App.loadThing(result.args.thingId, App.tabs[5]); 127 | break; 128 | } 129 | } 130 | else { 131 | console.log('Transfer error: ' + error.message); 132 | } 133 | }); 134 | App.initAccount(); 135 | App.initThingFactory(App.config.defaultTradeCenterAccount, App.config.defaultTradeCenterThingsNum); 136 | App.initThingFactory(App.config.defaultBreedCenterAccount, App.config.defaultBreedCenterThingsNum); 137 | App.initThingFactory(App.config.defaultFightCenterAccount, App.config.defaultFightCenterThingsNum); 138 | for (let i = 0; i < App.config.defaultUsersAccount.length; i++) { 139 | App.initThingFactory(App.config.defaultUsersAccount[i], App.config.defaultUsersThingsNum); 140 | } 141 | // Update UI 142 | return App.handleTradeCenter(); 143 | }); 144 | 145 | return App.bindEvents(); 146 | }, 147 | 148 | // 初始化帐号相关 149 | initAccount: function () { 150 | let menuRow = $('#menuRow'); 151 | let li; 152 | for (let i = 0; i < App.config.defaultUsersAccount.length; i++) { 153 | if (li) { 154 | li = li + ` `; 157 | } 158 | else { 159 | li = ` `; 162 | } 163 | } 164 | menuRow.append(li); 165 | // 更新帐号余额 166 | App.updateBalance(); 167 | }, 168 | 169 | // 初始化帐号的产品,写入区块链 170 | initThingFactory: function (account, num) { 171 | App.contracts.ThingCore.deployed().then(function (instance) { 172 | return instance.getThingsByOwner(account); 173 | }).then(function (result) { 174 | if (result.length < num) { 175 | for (let i = 0; i < (num - result.length); i++) { 176 | App.api.createRandomThing(Math.random().toString(36).substr(2), 177 | parseInt(num, 10), {from: account, gas: 100000000}); 178 | } 179 | } 180 | 181 | console.log('initThingFactory for ' + account); 182 | }).catch(function (err) { 183 | console.log('initThingFactory error, account: ' + account + ', num: ' + num 184 | + ', error: ' + err.message); 185 | }); 186 | }, 187 | 188 | // 交易大厅 189 | handleTradeCenter: function () { 190 | App.currentTab = App.tabs[0]; 191 | $('#play-hint').text('玩法:(1)点击购买宠物(2)数据入链后交易才完成(3)在“我的“中查看已购宠物').show(); 192 | $('#thingsRow').empty(); 193 | App.contracts.ThingCore.deployed().then(function (instance) { 194 | return instance.getThingsByOwner(App.config.defaultTradeCenterAccount); 195 | }).then(function (thingIds) { 196 | for (let i = 0; i < thingIds.length; i++) { 197 | App.loadThing(thingIds[i], App.tabs[0]); 198 | } 199 | }).catch(function (err) { 200 | console.log('handleTradeCenter error: ' + err.message); 201 | }); 202 | }, 203 | 204 | // 繁育中心 205 | handleBreedCenter: function () { 206 | App.currentTab = App.tabs[1]; 207 | $('#play-hint').text('玩法:(1)在希望交配的宠物下输入我的宠物ID(2)点击选TA(3)数据入链后,交配产生的新宠物在“我的“中查看').show(); 208 | $('#thingsRow').empty(); 209 | App.contracts.ThingCore.deployed().then(function (instance) { 210 | return instance.getThingsByOwner(App.config.defaultBreedCenterAccount); 211 | }).then(function (thingIds) { 212 | for (let i = 0; i < thingIds.length; i++) { 213 | App.loadThing(thingIds[i], App.tabs[1]); 214 | } 215 | }).catch(function (err) { 216 | console.log('handleBreedCenter error: ' + err.message); 217 | }); 218 | }, 219 | 220 | // 战斗中心 221 | handleFightCenter: function () { 222 | App.currentTab = App.tabs[2]; 223 | $('#play-hint').text('玩法:(1)在希望与之战斗的宠物下输入我的宠物ID(2)点击打TA触发战斗(3)数据入链后,战斗结果在“我的“中查看').show(); 224 | $('#thingsRow').empty(); 225 | App.contracts.ThingCore.deployed().then(function (instance) { 226 | return instance.getThingsByOwner(App.config.defaultFightCenterAccount); 227 | }).then(function (thingIds) { 228 | for (let i = 0; i < thingIds.length; i++) { 229 | App.loadThing(thingIds[i], App.tabs[2]); 230 | } 231 | }).catch(function (err) { 232 | console.log('handleFightCenter error: ' + err.message); 233 | }); 234 | }, 235 | 236 | // 喂养中心 237 | handleFeedCenter: function () { 238 | App.currentTab = App.tabs[3]; 239 | $('#play-hint').text('玩法:(1)给你的宠物喂食以太猫(2)在希望被喂食的宠物下输入以太猫ID,介于1~60000之间,点击“”喂食”(3)数据入链后,在“我的“中查看产生的新的变异宠物').show(); 240 | $('#thingsRow').empty(); 241 | App.contracts.ThingCore.deployed().then(function (instance) { 242 | return instance.getThingsByOwner(App.currentAccount); 243 | }).then(function (thingIds) { 244 | for (let i = 0; i < thingIds.length; i++) { 245 | App.loadThing(thingIds[i], App.tabs[3]); 246 | } 247 | }).catch(function (err) { 248 | console.log('handleFeedCenter error: ' + err.message); 249 | }); 250 | }, 251 | 252 | // 升级中心 253 | handleUpgradeCenter: function () { 254 | App.currentTab = App.tabs[4]; 255 | $('#play-hint').text('玩法:升级宠物需花费 1ETH').show(); 256 | $('#thingsRow').empty(); 257 | App.contracts.ThingCore.deployed().then(function (instance) { 258 | return instance.getThingsByOwner(App.currentAccount); 259 | }).then(function (thingIds) { 260 | for (let i = 0; i < thingIds.length; i++) { 261 | App.loadThing(thingIds[i], App.tabs[4]); 262 | } 263 | }).catch(function (err) { 264 | console.log('handleUpgradeCenter error: ' + err.message); 265 | }); 266 | }, 267 | 268 | // 我的 269 | handleMyCenter: function () { 270 | App.currentTab = App.tabs[5]; 271 | $('#play-hint').hide(); 272 | $('#thingsRow').empty(); 273 | App.contracts.ThingCore.deployed().then(function (instance) { 274 | return instance.getThingsByOwner(App.currentAccount); 275 | }).then(function (thingIds) { 276 | for (let i = 0; i < thingIds.length; i++) { 277 | App.loadThing(thingIds[i], App.tabs[5]); 278 | } 279 | }).catch(function (err) { 280 | console.log('updateUIInTradeCenter error: ' + err.message); 281 | }); 282 | }, 283 | 284 | handleChangeAccount: function () { 285 | console.log('handleChangeAccount'); 286 | App.currentAccount = $(this).html(); 287 | console.log('handleChangeAccount text: ' + $(this).html()); 288 | $('#current-account').text(App.currentAccount); 289 | App.updateBalance(); 290 | }, 291 | 292 | // 购买 293 | handleBuyThing: function () { 294 | if (parseInt($(this).attr('thing-price'), 10) > App.currentAccountBalance) { 295 | alert('当前账户余额不足'); 296 | } 297 | 298 | let thingId = $(this).attr('thing-id'); 299 | let thingPrice = $(this).attr('thing-price'); 300 | $('[thing-item-id=' + thingId + ']').find('.btn-bug').text('购买中').attr('disabled', true); 301 | App.contracts.ThingCore.deployed().then(function (instance) { 302 | if (App.config.debug) { 303 | console.log(App.currentAccount + ' buy thing, thingId: ' + thingId + ', thingPrice: ' + thingPrice); 304 | } 305 | 306 | web3.eth.sendTransaction({ 307 | from: App.currentAccount, 308 | to: App.config.defaultTradeCenterAccount, 309 | value: web3.toWei(thingPrice, 'ether') 310 | }); 311 | return instance.buyThing(thingId, { 312 | from: App.currentAccount 313 | }); 314 | }).then(function (result) { 315 | if (App.config.debug) { 316 | console.log('handleBuyThing result = ' + JSON.stringify(result)); 317 | } 318 | 319 | }).catch(function (err) { 320 | console.log(err.message); 321 | }); 322 | }, 323 | 324 | // 繁育 325 | handleBreed: function () { 326 | let targetThingId = $(this).attr('thing-id'); 327 | let myId = $('[thing-item-id=' + targetThingId + ']').find('.my-id').val(); 328 | if (myId === '') { 329 | alert('请输入你的宠物ID'); 330 | return; 331 | } 332 | 333 | App.contracts.ThingCore.deployed().then(function (instance) { 334 | return instance.getThing(parseInt(myId, 10)); 335 | }).then(function (thing) { 336 | let readyTime = thing[4]; 337 | let timestamp = new Date().getTime() / 1000; 338 | if (timestamp >= readyTime) { 339 | $('[thing-item-id=' + targetThingId + ']').find('.btn-breed').text('交配中').attr('disabled', true); 340 | App.contracts.ThingCore.deployed().then(function (instance) { 341 | if (App.config.debug) { 342 | console.log(App.currentAccount + ' bread thing, targetThingId: ' + targetThingId + ', myId: ' + myId); 343 | } 344 | 345 | return instance.breed(parseInt(myId, 10), parseInt(targetThingId, 10), {from: App.currentAccount, gas: 1000000000}); 346 | }).then(function (result) { 347 | if (App.config.debug) { 348 | console.log('handleBreed result = ' + JSON.stringify(result)); 349 | } 350 | 351 | $('[thing-item-id=' + targetThingId + ']').find('.btn-breed').text('选TA').attr('disabled', false); 352 | alert('繁育完成,请在“我的”中查看结果。'); 353 | }).catch(function (err) { 354 | console.log(err.message); 355 | }); 356 | } 357 | else { 358 | alert('宠物还在冷却中,请换个'); 359 | } 360 | }).catch(function (err) { 361 | console.log(err.message); 362 | }); 363 | }, 364 | 365 | // 战斗 366 | handleFight: function () { 367 | let targetThingId = $(this).attr('thing-id'); 368 | let myId = $('[thing-item-id=' + targetThingId + ']').find('.my-id').val(); 369 | if (myId === '') { 370 | alert('请输入你的宠物ID'); 371 | return; 372 | } 373 | 374 | App.contracts.ThingCore.deployed().then(function (instance) { 375 | return instance.getThing(parseInt(myId, 10)); 376 | }).then(function (thing) { 377 | let readyTime = thing[4]; 378 | let timestamp = new Date().getTime() / 1000; 379 | if (timestamp >= readyTime) { 380 | $('[thing-item-id=' + targetThingId + ']').find('.btn-fight').text('战斗中').attr('disabled', true); 381 | App.contracts.ThingCore.deployed().then(function (instance) { 382 | if (App.config.debug) { 383 | console.log(App.currentAccount + ' fight thing, targetThingId: ' + targetThingId + ', myId: ' + myId); 384 | } 385 | 386 | return instance.attack(parseInt(myId, 10), parseInt(targetThingId, 10), {from: App.currentAccount, gas: 1000000000}); 387 | }).then(function (result) { 388 | if (App.config.debug) { 389 | console.log('handleBreed result = ' + JSON.stringify(result)); 390 | } 391 | 392 | $('[thing-item-id=' + targetThingId + ']').find('.btn-fight').text('打TA').attr('disabled', false); 393 | alert('战斗完成,请在“我的”中查看战斗结果。'); 394 | }).catch(function (err) { 395 | console.log(err.message); 396 | }); 397 | } 398 | else { 399 | alert('宠物还在冷却中,请换个'); 400 | } 401 | }).catch(function (err) { 402 | console.log(err.message); 403 | }); 404 | }, 405 | 406 | // 喂养 407 | handleFeed: function () { 408 | let targetThingId = $(this).attr('thing-id'); 409 | let kittyId = $('[thing-item-id=' + targetThingId + ']').find('.kitty-id').val(); 410 | if (kittyId === '') { 411 | alert('请输入食物(Kitty)的ID'); 412 | return; 413 | } 414 | 415 | App.contracts.ThingCore.deployed().then(function (instance) { 416 | return instance.getThing(parseInt(targetThingId, 10)); 417 | }).then(function (thing) { 418 | let readyTime = thing[4]; 419 | let timestamp = new Date().getTime() / 1000; 420 | if (timestamp >= readyTime) { 421 | $('[thing-item-id=' + targetThingId + ']').find('.btn-feed').text('喂食中').attr('disabled', true); 422 | App.contracts.ThingCore.deployed().then(function (instance) { 423 | if (App.config.debug) { 424 | console.log(App.currentAccount + ' feed thing, targetThingId: ' + targetThingId + ', kittyId: ' + kittyId); 425 | } 426 | 427 | return instance.feedOnKitty(parseInt(targetThingId, 10), parseInt(kittyId, 10), {from: App.currentAccount, gas: 1000000000}); 428 | }).then(function (result) { 429 | if (App.config.debug) { 430 | console.log('handleBreed result = ' + JSON.stringify(result)); 431 | } 432 | 433 | $('[thing-item-id=' + targetThingId + ']').find('.btn-feed').text('喂TA').attr('disabled', false); 434 | alert('喂食完成,请在“我的”中查看获得的变异宠物。'); 435 | }).catch(function (err) { 436 | console.log(err.message); 437 | }); 438 | } 439 | else { 440 | alert('宠物还在冷却中,请换个'); 441 | } 442 | }).catch(function (err) { 443 | console.log(err.message); 444 | }); 445 | }, 446 | 447 | // 升级 448 | handleUpgradeThing: function () { 449 | if (App.currentAccountBalance < 1) { 450 | alert('当前账户余额不足,升级至少需要1ETH'); 451 | } 452 | 453 | $(this).text('升级中').attr('disabled', true); 454 | let thingId = $(this).attr('thing-id'); 455 | App.contracts.ThingCore.deployed().then(function (instance) { 456 | if (App.config.debug) { 457 | console.log(App.currentAccount + ' upgrade thing, thingId: ' + thingId); 458 | } 459 | 460 | return instance.levelUp(thingId, { 461 | from: App.currentAccount, 462 | value: web3.toWei(App.config.levelUpFee, 'ether') 463 | }); 464 | }).then(function (result) { 465 | if (App.config.debug) { 466 | console.log('handleUpgradeThing result = ' + JSON.stringify(result)); 467 | } 468 | 469 | App.contracts.ThingCore.deployed().then(function (instance) { 470 | return instance.getThing(parseInt(thingId, 10)); 471 | }).then(function (thing) { 472 | $('[thing-item-id=' + thingId + ']').find('.thing-level').text(thing[3]); 473 | $('[thing-item-id=' + thingId + ']').find('.btn-upgrade').text('升级').attr('disabled', false); 474 | alert('升级完成,请在“我的”中查看结果。'); 475 | 476 | }); 477 | }).catch(function (err) { 478 | console.log(err.message); 479 | }); 480 | }, 481 | 482 | // 出售 483 | handleSellThing: function () { 484 | $(this).text('出售中').attr('disabled', true); 485 | let thingId = $(this).attr('thing-id'); 486 | let thingPrice = $(this).attr('thing-price'); 487 | App.contracts.ThingCore.deployed().then(function (instance) { 488 | if (App.config.debug) { 489 | console.log(App.currentAccount + ' sell thing, thingId: ' + thingId + ', thingPrice: ' + thingPrice); 490 | } 491 | 492 | web3.eth.sendTransaction({ 493 | from: App.config.defaultTradeCenterAccount, 494 | to: App.currentAccount, 495 | value: web3.toWei(thingPrice, 'ether') 496 | }); 497 | return instance.buyThing(thingId, { 498 | from: App.config.defaultTradeCenterAccount 499 | }); 500 | }).then(function (result) { 501 | if (App.config.debug) { 502 | console.log('handleBuyThing result = ' + JSON.stringify(result)); 503 | } 504 | 505 | }).catch(function (err) { 506 | console.log(err.message); 507 | }); 508 | }, 509 | 510 | bindEvents: function () { 511 | $(document).on('click', '.menu-item', App.handleChangeAccount); 512 | $('#trade-center').on('click', App.handleTradeCenter); 513 | $('#breed-center').on('click', App.handleBreedCenter); 514 | $('#fight-center').on('click', App.handleFightCenter); 515 | $('#feed-center').on('click', App.handleFeedCenter); 516 | $('#upgrade-center').on('click', App.handleUpgradeCenter); 517 | $('#my-center').on('click', App.handleMyCenter); 518 | 519 | $(document).on('click', '.btn-bug', App.handleBuyThing); 520 | $(document).on('click', '.btn-upgrade', App.handleUpgradeThing); 521 | $(document).on('click', '.btn-sell', App.handleSellThing); 522 | $(document).on('click', '.btn-breed', App.handleBreed); 523 | $(document).on('click', '.btn-fight', App.handleFight); 524 | $(document).on('click', '.btn-feed', App.handleFeed); 525 | }, 526 | 527 | updateBalance: function () { 528 | let balance = web3.fromWei(web3.eth.getBalance(App.currentAccount), 'ether'); 529 | App.currentAccountBalance = balance; 530 | $('#account-balance').text(balance + ' ETH'); 531 | }, 532 | 533 | generateAttr: function (dna) { 534 | 535 | let dnaStr = String(dna); 536 | // 如果dna少于16位,在它前面用0补上 537 | while (dnaStr.length < 16) { 538 | dnaStr = '0' + dnaStr; 539 | } 540 | return { 541 | // 前两位数构成头部.我们可能有7种头部, 所以 % 7 542 | // 得到的数在0-6,再加上1,数的范围变成1-7 543 | // 通过这样计算: 544 | headChoice: dnaStr.substring(0, 2) % 7 + 1, 545 | // 我们得到的图片名称从head1.png 到 head7.png 546 | 547 | // 接下来的两位数构成眼睛, 眼睛变化就对11取模: 548 | eyeChoice: dnaStr.substring(2, 4) % 11 + 1, 549 | // 再接下来的两位数构成衣服,衣服变化就对6取模: 550 | skinChoice: dnaStr.substring(4, 6) % 6 + 1, 551 | 552 | upChoice: dnaStr.substring(6, 8), 553 | downChoice: dnaStr.substring(8, 10) 554 | }; 555 | 556 | }, 557 | 558 | loadThing: function (thingId, targetTab) { 559 | App.contracts.ThingCore.deployed().then(function (instance) { 560 | return instance.getThing(parseInt(thingId, 10)); 561 | }).then(function (thing) { 562 | if ($('#page-hint').is(':visible')) { 563 | $('#pageTitle').text(App.config.dappName); 564 | $('#page-hint').hide(); 565 | $('#page-tabs').show(); 566 | $('#page-head').show(); 567 | } 568 | 569 | let name = thing[0]; 570 | let price = thing[1]; 571 | let dna = thing[2]; 572 | let level = thing[3]; 573 | let readyTime = thing[4]; 574 | let generation = thing[5]; 575 | let winCount = thing[6]; 576 | let lossCount = thing[7]; 577 | console.log('dna: '+dna) 578 | let url = '/images/' + ((dna % 10086) % 100).toString() + '.svg'; 579 | if (App.config.debug) { 580 | console.log('Image res: ' + url); 581 | } 582 | 583 | console.log(JSON.stringify(thing)); 584 | let thingsRow = $('#thingsRow'); 585 | let thingTemplate = $('#thing-template'); 586 | thingTemplate.find('.thing-template-body').addClass('thing-item'); 587 | thingTemplate.find('.thing-template-body').attr('thing-item-id', thingId); 588 | thingTemplate.find('.panel-title').text('名字:' + name); 589 | thingTemplate.find('img').attr('src', url); 590 | thingTemplate.find('.thing-id').text(thingId); 591 | thingTemplate.find('.thing-price').text(price); 592 | thingTemplate.find('.thing-level').text(level); 593 | thingTemplate.find('.thing-generation').text(generation); 594 | let timestamp = new Date().getTime() / 1000; 595 | if (timestamp >= readyTime) { 596 | thingTemplate.find('.thing-ready-time').text(0); 597 | } 598 | else { 599 | thingTemplate.find('.thing-ready-time').text(parseInt((readyTime - timestamp) / 60, 10)); 600 | } 601 | thingTemplate.find('.thing-fight-win').text(winCount); 602 | thingTemplate.find('.thing-fight-loss').text(lossCount); 603 | let attr = App.generateAttr(thing[2]); 604 | thingTemplate.find('.thing-head').text(attr.headChoice); 605 | thingTemplate.find('.thing-eye').text(attr.eyeChoice); 606 | thingTemplate.find('.thing-skin').text(attr.skinChoice); 607 | thingTemplate.find('.thing-up').text(attr.upChoice); 608 | thingTemplate.find('.thing-down').text(attr.downChoice); 609 | thingTemplate.find('.btn-bug').attr('thing-id', thingId); 610 | thingTemplate.find('.btn-bug').attr('thing-price', price); 611 | thingTemplate.find('.btn-sell').attr('thing-id', thingId); 612 | thingTemplate.find('.btn-sell').attr('thing-price', price); 613 | thingTemplate.find('.btn-upgrade').attr('thing-id', thingId); 614 | thingTemplate.find('.btn-breed').attr('thing-id', thingId); 615 | thingTemplate.find('.btn-fight').attr('thing-id', thingId); 616 | thingTemplate.find('.btn-feed').attr('thing-id', thingId); 617 | if (App.currentTab !== targetTab) { 618 | return; 619 | } 620 | 621 | switch (App.currentTab) { 622 | case App.tabs[0]: 623 | thingTemplate.find('.btn-bug').show(); 624 | thingTemplate.find('.btn-sell').hide(); 625 | thingTemplate.find('.btn-upgrade').hide(); 626 | thingTemplate.find('.btn-breed').hide(); 627 | thingTemplate.find('.btn-fight').hide(); 628 | thingTemplate.find('.my-id').hide(); 629 | thingTemplate.find('.btn-feed').hide(); 630 | thingTemplate.find('.kitty-id').hide(); 631 | break; 632 | case App.tabs[1]: 633 | thingTemplate.find('.btn-bug').hide(); 634 | thingTemplate.find('.btn-sell').hide(); 635 | thingTemplate.find('.btn-upgrade').hide(); 636 | thingTemplate.find('.btn-breed').show(); 637 | thingTemplate.find('.btn-fight').hide(); 638 | thingTemplate.find('.my-id').show(); 639 | thingTemplate.find('.btn-feed').hide(); 640 | thingTemplate.find('.kitty-id').hide(); 641 | break; 642 | case App.tabs[2]: 643 | thingTemplate.find('.btn-bug').hide(); 644 | thingTemplate.find('.btn-sell').hide(); 645 | thingTemplate.find('.btn-upgrade').hide(); 646 | thingTemplate.find('.btn-breed').hide(); 647 | thingTemplate.find('.btn-fight').show(); 648 | thingTemplate.find('.my-id').show(); 649 | thingTemplate.find('.btn-feed').hide(); 650 | thingTemplate.find('.kitty-id').hide(); 651 | break; 652 | case App.tabs[3]: 653 | thingTemplate.find('.btn-bug').hide(); 654 | thingTemplate.find('.btn-sell').hide(); 655 | thingTemplate.find('.btn-upgrade').hide(); 656 | thingTemplate.find('.btn-breed').hide(); 657 | thingTemplate.find('.btn-fight').hide(); 658 | thingTemplate.find('.my-id').hide(); 659 | thingTemplate.find('.btn-feed').show(); 660 | thingTemplate.find('.kitty-id').show(); 661 | break; 662 | case App.tabs[4]: 663 | thingTemplate.find('.btn-bug').hide(); 664 | thingTemplate.find('.btn-sell').hide(); 665 | thingTemplate.find('.btn-upgrade').show(); 666 | thingTemplate.find('.btn-breed').hide(); 667 | thingTemplate.find('.btn-fight').hide(); 668 | thingTemplate.find('.my-id').hide(); 669 | thingTemplate.find('.btn-feed').hide(); 670 | thingTemplate.find('.kitty-id').hide(); 671 | break; 672 | case App.tabs[5]: 673 | thingTemplate.find('.btn-bug').hide(); 674 | thingTemplate.find('.btn-sell').show(); 675 | thingTemplate.find('.btn-upgrade').hide(); 676 | thingTemplate.find('.btn-breed').hide(); 677 | thingTemplate.find('.btn-fight').hide(); 678 | thingTemplate.find('.my-id').hide(); 679 | thingTemplate.find('.btn-feed').hide(); 680 | thingTemplate.find('.kitty-id').hide(); 681 | break; 682 | } 683 | thingsRow.append(thingTemplate.html()); 684 | }).catch(function (err) { 685 | console.log('loadThing error: ' + err.message); 686 | }); 687 | }, 688 | 689 | removeThing: function (thingId, targetTab) { 690 | console.log('start to remove ' + thingId + ' in tab ' + targetTab); 691 | if (App.currentTab !== targetTab) { 692 | console.log('not current tab, return'); 693 | return; 694 | } 695 | 696 | $('[thing-item-id=' + thingId + ']').remove(); 697 | } 698 | 699 | }; 700 | 701 | $(function () { 702 | $(window).load(function () { 703 | App.init(); 704 | }); 705 | }); 706 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug":true, 3 | "dapp_name": "CryptoKitties", 4 | "rpc": "http://127.0.0.1:8545", 5 | "network_id":"15", 6 | "default_trade_center_things_num": 6, 7 | "default_breed_center_things_num": 4, 8 | "default_fight_center_things_num": 4, 9 | "default_users_things_num": 1, 10 | "default_accounts": { 11 | "trade_center":"0x673751313d2E477F789F0a1f0A4Fc4208ba908b3", 12 | "breed_center":"0xcf7EC987043f1314F2BB7ba8963fcEA2e15a1b18", 13 | "fight_center":"0x44fE4bce1867C7bA318aA6A7bEB1d0795db3cA62", 14 | "feed_center":"0xe9B1ADcFfF8cAbA1371C10210FFB49A91058d7b9", 15 | "upgrade_center":"0xa7383b6C584f10f3e7f870AcB3495C9B907264f1", 16 | "users":[ 17 | "0xEc0D61b303C2ECEd7d18B166D70f7C7DC69c379B", 18 | "0xFCed13a4232257B371c910293A840EAC1863FF0A" 19 | ] 20 | }, 21 | "level_up_fee":"1" 22 | } -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello2mao/CryptoKitties/2c08114154442eacc71fb59af89ee78f4692304d/src/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello2mao/CryptoKitties/2c08114154442eacc71fb59af89ee78f4692304d/src/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello2mao/CryptoKitties/2c08114154442eacc71fb59af89ee78f4692304d/src/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello2mao/CryptoKitties/2c08114154442eacc71fb59af89ee78f4692304d/src/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/images/0.svg: -------------------------------------------------------------------------------- 1 | NoSuchKeyThe specified key does not exist.
No such object: ck-kitty-image/0x06012c8cf97bead5deae237070f9587f8e7a266d/0.svg
-------------------------------------------------------------------------------- /src/images/41.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/50.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/91.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ETH ERC721 DAPP Demo 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 |
21 |
22 | 50 | 51 |
52 |
53 |

54 |
55 |
56 |
57 | 58 | 68 | 69 |
70 |

区块链数据查询中....

71 |
72 |
73 |

<<====交易大厅====>>

74 |

<<====繁育中心====>>

75 |

<<====对战中心====>>

76 |

<<====喂养中心====>>

77 |

<<====升级中心====>>

78 |
79 | 80 | 81 |
82 |
83 | 84 |
85 |
86 | 87 | 88 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let app = express(); 3 | 4 | app.use(express.static("./")); 5 | 6 | app.listen(3000, function() { 7 | console.log("Dapp server start on port: 3000"); 8 | }); -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "127.0.0.1", 5 | port: 8545, 6 | from: "0x3A834e8c4ab7CfF61C11F71129205020bd828533", 7 | // gas: 4712388, // web3.eth.getBlock("pending").gasLimit 8 | network_id: "*" 9 | } 10 | } 11 | }; 12 | --------------------------------------------------------------------------------