Compare commits

...

2 Commits

Author SHA1 Message Date
9436494d69
feat: complete SBT verification 2023-06-09 00:04:06 +08:00
46c62eb255
feat: SBT frontpage 2023-06-08 05:09:09 +08:00
6 changed files with 772 additions and 27 deletions

653
src/assets/SBT.json Normal file
View File

@ -0,0 +1,653 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "client",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "bank",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Borrow",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "client",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "bank",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Repay",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "client",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "bank",
"type": "address"
}
],
"name": "Warning",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "player",
"type": "address"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "bank",
"type": "address"
}
],
"name": "addReliableBank",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "bank",
"type": "address"
}
],
"name": "removeReliableBank",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
},
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "logBorrowing",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
},
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "logRepaying",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "logWarning",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "addr",
"type": "address"
},
{
"internalType": "uint256",
"name": "number",
"type": "uint256"
}
],
"name": "addCertificate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "listCertificate",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "addr",
"type": "address"
},
{
"internalType": "uint256",
"name": "number",
"type": "uint256"
}
],
"internalType": "struct SoulboundToken.Certificate[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getAccountNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
}
]

View File

@ -39,6 +39,11 @@ hr {
background: #060606; background: #060606;
color: white; color: white;
} */ } */
.box.is-fullwidth h1.title.is-4{
color: #060606;
}
.footer { .footer {
background: #060606; background: #060606;
color: white; color: white;
@ -111,3 +116,4 @@ hr {
height: 380px; height: 380px;
} }
::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;border-radius:6px}::-webkit-scrollbar-thumb{background:#d5d5d5;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#c4c4c4} ::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;border-radius:6px}::-webkit-scrollbar-thumb{background:#d5d5d5;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#c4c4c4}

View File

@ -24,7 +24,7 @@ export default {
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<!-- Content ... --> <!-- Content ... -->
<p>{{ successMsg }}</p> <span v-html="successMsg"></span>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<RouterLink :to="link" class="button is-success" @click="$emit('closeModal')">{{ btnName }}</RouterLink> <RouterLink :to="link" class="button is-success" @click="$emit('closeModal')">{{ btnName }}</RouterLink>

View File

@ -32,12 +32,12 @@
<p class="post-excerpt">在區塊鏈世代中使用最方便的支付方式透過具有公信力的 Soulbound Token <p class="post-excerpt">在區塊鏈世代中使用最方便的支付方式透過具有公信力的 Soulbound Token
(SBT)進行信用認證方便信用評分直接透過區塊鏈銀行進行消費QRcode掃完就走</p> (SBT)進行信用認證方便信用評分直接透過區塊鏈銀行進行消費QRcode掃完就走</p>
<br> <br>
<a href="#" class="button is-info"> <RouterLink to="/signup" class="button is-info">
<p>馬上申辦</p> <p>馬上申辦</p>
<span class="icon"> <span class="icon">
<i class="fas fa-arrow-right"></i> <i class="fas fa-arrow-right"></i>
</span> </span>
</a> </RouterLink>
</div> </div>
</div> </div>

View File

@ -1,37 +1,58 @@
<script> <script>
import Web3 from 'web3'; import Web3 from 'web3';
import SBT from '@/assets/SBT.json' import SBT from '@/assets/SBT.json'
import WarningModal from '../components/WarningModal.vue' import WarningModal from '../components/WarningModal.vue'
import SuccessModal from '../components/SuccessModal.vue' import SuccessModal from '../components/SuccessModal.vue'
import PageTitle from '../components/PageTitle.vue'
import { useClientStore } from '../stores/Client.js' import { useClientStore } from '../stores/Client.js'
export default { export default {
components: { WarningModal, SuccessModal }, components: { WarningModal, SuccessModal, PageTitle },
name: 'PageFooter', name: 'PageFooter',
data () { data() {
return { return {
SBTAddress: import.meta.env.VITE_SBT_ADDR, SBTAddress: import.meta.env.VITE_SBT_ADDR,
number: '', number: 0,
warningModalStatus: false, warningModalStatus: false,
successModalStatus: false, successModalStatus: false,
msg: '', msg: '',
clientAddr: '',
web3: null,
token: null,
isWaiting: false
} }
}, },
async mounted() {
this.web3 = new Web3(window.ethereum)
this.clientAddr = (await this.web3.eth.getAccounts())[0]
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
},
methods: { methods: {
async check () { async check() {
console.log(this.SBTAddress) var returnNumber = await this.token.methods.getAccountNumber(this.clientAddr).call()
const web3 = new Web3(window.ethereum) if (returnNumber != 0 && returnNumber == this.number) {
const clientAddr = (await web3.eth.getAccounts())[0]
var token = new web3.eth.Contract(SBT, this.SBTAddress)
var returnNumber = await token.methods.getAccountNumber(clientAddr).call()
if (returnNumber != 0 && returnNumber == number) {
this.successModalStatus = true this.successModalStatus = true
this.msg = 'Success: '+returnNumber this.msg = '驗證成功!'
} else { } else {
this.warningModalStatus = true this.warningModalStatus = true
this.msg = 'Fail: '+returnNumber this.msg = '連接失敗,這並不是你的 SBT number'
} }
},
async mint() {
this.isWaiting = true
try {
var result = await this.token.methods.mint(this.clientAddr).send({ from: this.clientAddr })
this.successModalStatus = true
var returnNumber = await this.token.methods.getAccountNumber(this.clientAddr).call()
this.msg = 'mint 成功! <a href="https://sepolia.etherscan.io/tx/' + result.transactionHash + '">etherscan</a><br>您的 SBT number 是 ' + returnNumber
} catch (error) {
this.warningModalStatus = true
this.msg = 'mint 失敗,您可能已經持有 SBT請在左方輸入 SBT number 進行驗證'
}
this.isWaiting = false
} }
} }
} }
@ -39,15 +60,80 @@ export default {
</script> </script>
<template> <template>
<button class='button is-info is-large'>mint</button> <section class="blog-posts">
<hr> <div class="container">
<form> <div class="block">
number: <input type='text' v-model='number'> <PageTitle title="Set up Soulbound Token(SBT)" subtitle="Mint SBT 紀錄個人信用並作為身份驗證憑證"></PageTitle>
<input type='submit' class='button is-success'> </div>
</form> <div class="block">
<button class='button is-info is-large' @click='check'>check</button> <div class="columns">
<div class="column is-6">
<div class="box is-fullwidth">
<div class="block">
<h1 class="title is-4">您已經擁有 SBT</h1>
</div>
<div class="block">
請輸入您的 SBT 相關資訊
</div>
<div class="block">
<div class="field">
<label class="label">SBT address</label>
<div class="control">
<input class="input is-info is-rounded" type="text" :value="SBTAddress" disabled>
</div>
</div>
<div class="field">
<label class="label">Username</label>
<div class="control has-icons-left has-icons-right">
<input class="input is-info is-rounded" type="number" placeholder="Token number" v-model="number">
<span class="icon is-small is-left">
<i class="fas fa-user"></i>
</span>
<span class="icon is-small is-right">
<!-- <i class="fas fa-check"></i> -->
</span>
</div>
<!-- <p class="help is-success">This username is available</p> -->
</div>
<div class="column is-2 is-offset-5">
<button class="button is-info is-outlined" @click="check">驗證</button>
</div>
</div>
</div>
</div>
<div class="column is-6">
<div class="box is-fullwidth">
<div class="block">
<h1 class="title is-4">您還沒有 SBT</h1>
</div>
<div class="block">
向下方 SBT 智能合約地址請求 Mint 一個 Soulbound Token 到你的地址
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus=false"></WarningModal> </div>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus=false" link="/signup/linksbt" btnName="繼續"></SuccessModal> <div class="block">
<div class="field">
<label class="label">SBT address</label>
<div class="control">
<input class="input is-info is-rounded" type="text" :value="SBTAddress" disabled>
</div>
</div>
<div class="column is-2 is-offset-5">
<template v-if="!isWaiting">
<button class="button is-info is-outlined" @click="mint">MINT</button>
</template>
<template v-else>
<button class="button is-info is-outlined is-loading"></button>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus = false"></WarningModal>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus = false"
link="/signup/credit" btnName="繼續"></SuccessModal>
</template> </template>

View File

@ -25,14 +25,14 @@ export default {
const account = await window.ethereum.request({ method: 'eth_requestAccounts' }) const account = await window.ethereum.request({ method: 'eth_requestAccounts' })
this.client.address = account[0] this.client.address = account[0]
this.client.linked = true this.client.linked = true
this.msg = 'Success: 已經成功連接 MetaMask繼續完成 Soulbound Token 設定' this.msg = '已經成功連接 MetaMask繼續完成 Soulbound Token 設定'
this.successModalStatus = true this.successModalStatus = true
} else { } else {
this.msg = 'ERROR: 你連接的不是 Sepolia 測試網路,目前只接受 Sepolia address' this.msg = '你連接的不是 Sepolia 測試網路,目前只接受 Sepolia address'
this.warningModalStatus = true this.warningModalStatus = true
} }
} else { } else {
this.msg = 'ERROR: no Metamask' this.msg = 'no Metamask'
this.warningModalStatus = true this.warningModalStatus = true
} }
} }