├── .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 | 
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 | NoSuchKey
The 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 |
23 |
24 |
25 |
26 | 0 ETH
27 |
28 |
31 |
32 |
33 |
34 | 0x0
35 |
36 |
37 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
区块链数据查询中....
71 |
72 |
73 | <<====交易大厅====>>
74 | <<====繁育中心====>>
75 | <<====对战中心====>>
76 | <<====喂养中心====>>
77 | <<====升级中心====>>
78 |
79 |
玩法:
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
Scrappy
93 |
94 |
95 |
![395x343]()
97 |
98 |
ID:
3
99 |
价格:
3 ETH
100 |
等级:
3 级
101 |
代数:
0 代
102 |
冷却:
3 分钟
103 |
战斗:
104 |
105 | 胜利次数: 1 失败次数: 1
106 |
107 |
属性:
108 |
109 | 头部基因: 1 眼部基因: 1
110 |
111 |
112 | 皮肤基因: 1 上身基因: 1
113 |
114 |
115 | 下身基因: 1
116 |
117 |
118 |
120 |
122 |
124 |
126 |
128 |
129 |
130 |
132 |
133 |
134 |
135 |
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 |
--------------------------------------------------------------------------------