update numbergame bapp

This commit is contained in:
elegant651
2020-03-26 15:16:54 +09:00
parent 6a95428c6b
commit c1e2973803
21 changed files with 4853 additions and 3141 deletions

View File

@@ -1,8 +1,8 @@
pragma solidity ^0.4.18; pragma solidity >=0.4.24 <=0.5.6;
contract Ownable { contract Ownable {
address owner; address owner;
function Ownable() public { constructor() public {
owner = msg.sender; owner = msg.sender;
} }
@@ -14,39 +14,36 @@ contract Ownable {
contract Mortal is Ownable { contract Mortal is Ownable {
function kill() public Owned { function kill() public Owned {
selfdestruct(owner); selfdestruct(msg.sender);
} }
} }
contract Betting is Mortal { contract Game is Mortal {
uint minBet; // uint minBet; //
uint winRate; // (%)
event Won(bool _result, uint _amount); event Won(bool _result, uint _amount);
function Betting(uint _minBet, uint _winRate) payable public { constructor(uint _minBet) payable public {
require(_minBet > 0); require(_minBet > 0);
require(_winRate <= 100); minBet = _minBet;
minBet = _minBet;
winRate = _winRate;
} }
function() public { function() external {
revert(); revert();
} }
function bet(uint _num) payable public { function play(uint _num) payable public {
require(_num > 0 && _num <= 5); require(_num > 0 && _num <= 5);
require(msg.value >= minBet); require(msg.value >= minBet);
uint winNum = random(); uint winNum = random();
if (_num == winNum) { if (_num == winNum) {
uint amtWon = msg.value * (100 - winRate)/10; uint amtWon = msg.value * 2;
if(!msg.sender.send(amtWon)) revert(); if(!msg.sender.send(amtWon)) revert();
Won(true, amtWon); emit Won(true, amtWon);
} else { } else {
Won(false, 0); emit Won(false, 0);
} }
} }
@@ -55,7 +52,6 @@ contract Betting is Mortal {
} }
function random() public view returns (uint) { function random() public view returns (uint) {
return uint(keccak256(block.difficulty, block.number, now)) % 5 + 1; return uint(keccak256(abi.encodePacked(now, msg.sender))) % 5 + 1;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{ {
"name": "bet-dapp", "name": "numbergame-bapp",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -10,7 +10,7 @@
"vue": "^2.5.21", "vue": "^2.5.21",
"vue-router": "^3.0.2", "vue-router": "^3.0.2",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"web3": "^0.20.6" "caver-js": "^1.3.2"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^3.1.1", "@vue/cli-plugin-babel": "^3.1.1",

View File

@@ -1,57 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -1,23 +1,27 @@
<template> <template>
<div class="betting"> <div class="betting">
<h1>Bet Dapp Test</h1> <h1>Numbergame BApp Test</h1>
Amount: <input v-model="amount" placeholder="0 Ether"> Amount: <input v-model="amount" placeholder="0 Klay">
<ul> <ul>
<li v-on:click="_clickNumber">1</li> <li v-on:click="clickNumber">1</li>
<li v-on:click="_clickNumber">2</li> <li v-on:click="clickNumber">2</li>
<li v-on:click="_clickNumber">3</li> <li v-on:click="clickNumber">3</li>
<li v-on:click="_clickNumber">4</li> <li v-on:click="clickNumber">4</li>
<li v-on:click="_clickNumber">5</li> <li v-on:click="clickNumber">5</li>
</ul> </ul>
<div v-if="pending" id="loader">Loading...</div> <div v-if="pending" id="loader">Loading...</div>
<div class="event" v-if="winEvent"> <div class="event" v-if="winEvent">
Won: {{ winEvent._result }} Won: {{ winEvent.result }}
Amount: {{ winEvent._amount }} Wei Reward: {{ winEvent.amount }} Klay
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters, mapMutations } from 'vuex'
import {cav} from '@/klaytn/caver'
export default { export default {
name: 'betting-component', name: 'betting-component',
data() { data() {
@@ -26,38 +30,38 @@ export default {
pending: false, pending: false,
winEvent: null winEvent: null
} }
},
computed: {
...mapGetters('wallet', [
'klaytn'
])
}, },
mounted () { mounted () {
this.$store.dispatch('getContractInstance')
}, },
methods: { methods: {
_clickNumber (event) { clickNumber (event) {
console.log('betting number', event.target.innerHTML, this.amount) console.log('betting number', event.target.innerHTML, this.amount)
this.winEvent = null
this.pending = true this.pending = true
this.$store.state.contractInstance().bet(event.target.innerHTML, {
gas: 300000, this.winEvent = {}
value: this.$store.state.web3.web3Instance().toWei(this.amount, 'ether'), this.klaytn.play(event.target.innerHTML, this.amount, (receipt) => {
from: this.$store.state.web3.coinbase const result = receipt.events.Won.returnValues[0]
}, (err, result) => { const amount = receipt.events.Won.returnValues[1]
if (err) {
console.error(err) this.winEvent.result = result
this.pending = false this.winEvent.amount = cav.utils.fromPeb(amount, "KLAY")
} else {
const Won = this.$store.state.contractInstance().Won() this.$emit('complete-choose-number')
Won.watch((err, result) => {
if (err) { this.pending = false
console.error('won', error) }, (error) => {
} else { console.error(error)
this.winEvent = result.args this.pending = false
this.winEvent._amount = parseInt(result.args._amount, 10)
this.pending = false
}
})
}
}) })
} }
} }

View File

@@ -1,21 +0,0 @@
<template>
<div>
<DappMetamask />
<BettingComponent />
</div>
</template>
<script>
import DappMetamask from '@/components/dapp-metamask'
import BettingComponent from '@/components/betting-component'
export default {
name: 'betting-dapp',
beforeCreate () {
console.log('registerWeb3 Action dispatched')
this.$store.dispatch('registerWeb3')
},
components: {
DappMetamask,
BettingComponent
}
}
</script>

View File

@@ -1,20 +0,0 @@
<template>
<div class='metamask-info'>
<p>연결 상태 : {{ web3.isInjected }}</p>
<p>네트워크: {{ web3.networkId }}</p>
<p>코인베이스 주소: {{ web3.coinbase }}</p>
<p>잔액: {{ web3.balance }}</p>
</div>
</template>
<script>
export default {
name: 'dapp-metamask',
computed: {
web3 () {
return this.$store.state.web3
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div>
<Wallet />
<BettingComponent v-on:complete-choose-number="onCompleteChooseNum" />
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import KlaytnService from '@/klaytn/klaytnService'
import Wallet from '@/components/wallet'
import BettingComponent from '@/components/betting-component'
export default {
name: 'game-bapp',
components: {
Wallet,
BettingComponent
},
async mounted () {
await this.connect()
},
computed: {
...mapGetters('wallet', [
'klaytn',
'myaddress'
])
},
methods: {
...mapMutations('wallet', [
'setKlaytn',
'setIsConnectWallet',
'setMyAddress',
'setBalance'
]),
async connect () {
const klaytn = new KlaytnService()
this.setKlaytn(klaytn)
const address = await klaytn.init()
if (address) {
this.setMyAddress(address)
this.getBalance()
this.setIsConnectWallet(true)
} else {
this.setIsConnectWallet(false)
}
},
async getBalance () {
if (this.myaddress) {
const balance = await this.klaytn.getBalance(this.myaddress)
console.log(this.myaddress, balance)
this.setBalance(balance)
}
},
onCompleteChooseNum () {
this.getBalance()
}
}
}
</script>

View File

@@ -0,0 +1,151 @@
<template>
<div>
<h2>Connect to Wallet</h2>
<div>
<template v-if="walletInstance">
<div>
<h2>Integrated</h2>
<div class="balance">Balance: {{balance}} Klay</div>
<div class="address">{{walletInstance.address}}</div>
<button class="btnSubmit" @click="this.handleRemoveWallet">REMOVE WALLET</button>
</div>
</template>
<template v-else>
<div>
<label for="keystore">Keystore:</label>
<input type="file" name="keystore" v-on:change="this.handleImport" />
<label for="password">Password:</label>
<input
name="password"
v-model="password"
type="password"
required
></input>
<h3>OR</h3>
<label for="privateKey">Private Key:</label>
<input
v-model="privateKey"
name="privateKey"
type="password"
required
></input>
<p>
<button class="btnSubmit" @click="this.handleAddWallet">ADD WALLET</button>
</p>
</div>
</template>
</div>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
data: () => ({
// accessType: 'keystore',
keystore: null,
password: '',
privateKey: null,
walletInstance: null
}),
computed: {
...mapGetters('wallet', [
'klaytn',
'balance'
])
},
methods: {
...mapMutations('wallet', [
'setIsConnectWallet',
'setMyAddress',
'setBalance'
]),
validate () {
},
handleImport (e) {
const keystore = e.target.files[0]
const fileReader = new FileReader()
fileReader.onload = (e) => {
try {
if (!this.checkValidKeystore(e.target.result)) {
// If key store file is invalid, show message "Invalid keystore file."
alert('Invalid keystore file.')
return
}
this.keystore = e.target.result
} catch (e) {
alert('Invalid keystore file.')
return
}
}
fileReader.readAsText(keystore)
},
async handleAddWallet () {
try {
// Access type2: access thorugh private key
if(this.privateKey) {
await this.klaytn.integrateWallet(this.privateKey)
} else {
// Access type1: access through keystore + password
await this.klaytn.loginWithKeystore(this.keystore, this.password)
}
this.getWalletInfo()
} catch (e) {
alert(`Password or private key doesn't match.`)
}
},
async getWalletInfo () {
this.walletInstance = this.klaytn.getWallet()
const address = this.walletInstance.address
if(address) {
this.setMyAddress(address)
const balance = await this.klaytn.getBalance(address)
this.setBalance(balance)
this.setIsConnectWallet(true)
} else {
this.setIsConnectWallet(false)
}
},
checkValidKeystore (keystore) {
const parsedKeystore = JSON.parse(keystore)
const isValidKeystore = parsedKeystore.version &&
parsedKeystore.id &&
parsedKeystore.address &&
parsedKeystore.crypto
return isValidKeystore
},
async handleRemoveWallet () {
this.klaytn.removeWallet()
this.setIsConnectWallet(false)
this.walletInstance = null
}
},
mounted () {
if(this.klaytn) {
this.walletInstance = this.klaytn.getWallet()
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,27 @@
/**
* caver-js library helps making connection with klaytn node.
* You can connect to specific klaytn node by setting 'rpcURL' value.
* default rpcURL is 'https://api.baobab.klaytn.net:8651'.
*/
import Caver from 'caver-js'
const deployedABI = require('./deployedABI.json')
const TEST_NET = 'https://api.baobab.klaytn.net:8651'
export const config = {
rpcURL: TEST_NET
}
const DEPLOYED_ADDRESS = '0xa3e9E54627C6B7ED0986E5A9d1ecd7f8A0D013f2' // testnet
const cav = new Caver(config.rpcURL)
const getContractInstance = () => {
const contractInstance = deployedABI
&& DEPLOYED_ADDRESS
&& new cav.klay.Contract(deployedABI, DEPLOYED_ADDRESS)
return contractInstance
}
export {cav, getContractInstance}

View File

@@ -1,19 +1,4 @@
const address = '0x3bc507d62132520239a96a1c40b00086efbf3bf3' [
const ABI = [
{
"constant": false,
"inputs": [
{
"name": "_num",
"type": "uint256"
}
],
"name": "bet",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{ {
"constant": false, "constant": false,
"inputs": [], "inputs": [],
@@ -23,15 +8,25 @@ const ABI = [
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"constant": false,
"inputs": [
{
"name": "_num",
"type": "uint256"
}
],
"name": "play",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
"name": "_minBet", "name": "_minBet",
"type": "uint256" "type": "uint256"
},
{
"name": "_winRate",
"type": "uint256"
} }
], ],
"payable": true, "payable": true,
@@ -88,5 +83,4 @@ const ABI = [
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
} }
] ]
export {address, ABI}

View File

@@ -0,0 +1,97 @@
import { cav, getContractInstance } from './caver'
export default class KlaytnService {
constructor() {}
async init () {
const walletFromSession = sessionStorage.getItem('walletInstance')
// If 'walletInstance' value exists, add it to caver's wallet
if (walletFromSession) {
try {
const address = JSON.parse(walletFromSession).address
cav.klay.accounts.wallet.add(JSON.parse(walletFromSession))
return address
} catch (e) {
// If value in sessionStorage is invalid wallet instance,
// remove it from sessionStorage.
sessionStorage.removeItem('walletInstance')
return false
}
}
}
async getBlockNumber () {
const blockNumber = await cav.klay.getBlockNumber()
return blockNumber
}
async getBalance (address) {
const balance = await cav.klay.getBalance(address)
return cav.utils.fromPeb(balance, "KLAY")
}
async loginWithKeystore (keystore, password) {
const { privateKey: privateKeyFromKeystore } = cav.klay.accounts.decrypt(keystore, password)
await this.integrateWallet(privateKeyFromKeystore)
return true
}
integrateWallet (privateKey) {
const walletInstance = cav.klay.accounts.privateKeyToAccount(privateKey)
cav.klay.accounts.wallet.add(walletInstance)
sessionStorage.setItem('walletInstance', JSON.stringify(walletInstance))
return true
}
removeWallet () {
cav.klay.accounts.wallet.clear()
sessionStorage.removeItem('walletInstance')
return true
}
getWallet () {
if (cav.klay.accounts.wallet.length) {
return cav.klay.accounts.wallet[0]
}
return null
}
play (number, amount, dispatch, errorCb) {
const walletInstance = cav.klay.accounts.wallet && cav.klay.accounts.wallet[0]
if (!walletInstance) {
console.log('no walletInstance')
return
}
amount = cav.utils.toPeb(amount, "KLAY")
const address = walletInstance.address
getContractInstance().methods.play(number).send({
from: address,
gas: '100000000',
value: amount
})
.once('transactionHash', (txHash) => {
console.log(`
Sending a transaction...
txHash: ${txHash}
`
)
})
.once('receipt', (receipt) => {
console.log(`
Received receipt! (#${receipt.blockNumber} ,${receipt.transactionHash})
`, receipt)
dispatch(receipt)
})
.once('error', (error) => {
errorCb(error.message)
})
}
}

View File

@@ -1,7 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { store } from './store/' import store from './store'
Vue.config.productionTip = false Vue.config.productionTip = false

View File

@@ -1,9 +0,0 @@
import Web3 from 'web3'
import {address, ABI} from './betContract'
const getContract = new Promise(function (resolve, reject) {
const web3 = new Web3(window.web3.currentProvider)
const betContract = web3.eth.contract(ABI)
const betContractInstance = betContract.at(address)
resolve(betContractInstance)
})
export default getContract

View File

@@ -1,54 +0,0 @@
import Web3 from 'web3'
const getWeb3 = new Promise(function (resolve, reject) {
const web3js = window.web3
if (typeof web3js !== 'undefined') {
const web3 = new Web3(web3js.currentProvider)
resolve({
injectedWeb3: web3.isConnected(),
web3 () {
return web3
}
})
} else {
reject(new Error('Unable to connect to Metamask'))
}
})
.then(result => {
return new Promise(function (resolve, reject) {
result.web3().version.getNetwork((err, networkId) => {
if (err) {
reject(new Error('Unable to retrieve network ID'))
} else {
result = {...result, networkId}
resolve(result)
}
})
})
})
.then(result => {
return new Promise(function (resolve, reject) {
result.web3().eth.getCoinbase((err, coinbase) => {
if (err) {
reject(new Error('Unable to retrieve coinbase'))
} else {
result = {...result, coinbase}
resolve(result)
}
})
})
})
.then(result => {
return new Promise(function (resolve, reject) {
result.web3().eth.getBalance(result.coinbase, (err, balance) => {
if (err) {
reject(new Error('Unable to retrieve balance for address: ' + result.coinbase))
} else {
result = {...result, balance}
resolve(result)
}
})
})
})
export default getWeb3

View File

@@ -1,9 +0,0 @@
export const NETWORKS = {
'1': 'Main Net',
'2': 'Deprecated Morden test network',
'3': 'Ropsten test network',
'4': 'Rinkeby test network',
'42': 'Kovan test network',
'4447': 'Truffle Develop Network',
'5777': 'Ganache Blockchain'
}

View File

@@ -1,42 +0,0 @@
import Web3 from 'web3'
import {store} from '../store/'
const pollWeb3 = function (state) {
let web3 = window.web3
web3 = new Web3(web3.currentProvider)
setInterval(() => {
if (!web3 || !store.state.web3.web3Instance) {
return;
}
if (web3.eth.coinbase !== store.state.web3.coinbase) {
const newCoinbase = web3.eth.coinbase
web3.eth.getBalance(web3.eth.coinbase, function (err, newBalance) {
if (err) {
console.error(err)
} else {
store.dispatch('updateWeb3', {
coinbase: newCoinbase,
balance: parseInt(newBalance, 10)
})
}
})
} else {
web3.eth.getBalance(store.state.web3.coinbase, (err, newBalance) => {
if (err) {
console.log(err)
} else if (parseInt(newBalance, 10) !== store.state.web3.balance) {
store.dispatch('updateWeb3', {
coinbase: store.state.web3.coinbase,
balance: newBalance
})
}
})
}
}, 700)
}
export default pollWeb3

View File

@@ -1,14 +1,14 @@
import Vue from 'vue' import Vue from 'vue'
import Router from 'vue-router' import Router from 'vue-router'
import BettingDapp from '@/components/betting-dapp' import GameBapp from '@/components/game-bapp'
Vue.use(Router) Vue.use(Router)
export default new Router({ export default new Router({
routes: [ routes: [
{ {
path: '/', path: '/',
name: 'betting-dapp', name: 'game-bapp',
component: BettingDapp component: GameBapp
} }
] ]
}) })

View File

@@ -1,58 +1,12 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import state from './state'
import getWeb3 from '../network/getWeb3' import wallet from '@/store/modules/wallet'
import pollWeb3 from '../network/pollWeb3'
import getContract from '../network/getContract'
Vue.use(Vuex) Vue.use(Vuex)
export const store = new Vuex.Store({
strict: true,
state,
mutations: {
registerWeb3Instance (state, data) {
console.log('registerWeb3instance', data)
const result = data
let newWeb3 = state.web3
newWeb3.coinbase = result.coinbase
newWeb3.networkId = result.networkId
newWeb3.balance = parseInt(result.balance, 10)
newWeb3.isInjected = result.injectedWeb3
newWeb3.web3Instance = result.web3
state.web3 = newWeb3
pollWeb3()
},
updateWeb3Instance (state, data) { export default new Vuex.Store({
console.log('updateWeb3Instance', data) modules: {
state.web3.coinbase = data.coinbase wallet
state.web3.balance = parseInt(data.balance, 10) }
},
registerContractInstance (state, data) {
console.log('contract instance: ', data)
state.contractInstance = () => data
}
},
actions: {
registerWeb3 ({commit}) {
getWeb3.then(result => {
console.log('commit result')
commit('registerWeb3Instance', result)
}).catch(e => {
console.error('error', e)
})
},
updateWeb3 ({commit}, data) {
commit('updateWeb3Instance', data)
},
getContractInstance ({commit}) {
getContract.then(result => {
commit('registerContractInstance', result)
}).catch(e => console.log(e))
}
}
}) })

View File

@@ -0,0 +1,41 @@
const state = {
klaytn: null,
isConnectWallet: false,
myaddress: '',
balance: 0
}
const getters = {
klaytn: (state) => state.klaytn,
isConnectWallet: (state) => state.isConnectWallet,
myaddress: (state) => state.myaddress,
balance: (state) => state.balance
}
const mutations = {
setKlaytn(state, klaytn) {
state.klaytn = klaytn
},
setIsConnectWallet(state, isConnected) {
state.isConnectWallet = isConnected
},
setMyAddress(state, address) {
state.myaddress = address
},
setBalance(state, balance) {
state.balance = balance
}
}
const actions = {
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -1,12 +0,0 @@
const state = {
web3: {
isInjected: false,
web3Instance: null,
networkId: null,
coinbase: null,
balance: null,
error: null
},
contractInstance: null
}
export default state