add operations

This commit is contained in:
liangping 2021-08-26 21:50:56 +08:00
parent 81666c4132
commit 5de2c39d41
12 changed files with 2288 additions and 293 deletions

View File

@ -56,7 +56,6 @@ export async function signDirect(signer, signerAddress, messages, fee, memo, { a
throw new Error('Failed to retrieve account from signer')
}
const { pubkey } = accountFromSigner
console.log('accout:', accountFromSigner, pubkey)
const txBodyEncodeObject = {
typeUrl: '/cosmos.tx.v1beta1.TxBody',
value: {
@ -66,11 +65,8 @@ export async function signDirect(signer, signerAddress, messages, fee, memo, { a
}
const txBodyBytes = new Registry().encode(txBodyEncodeObject)
const gasLimit = Uint53.fromString(String(fee.gas))
console.log('account from signer: ', txBodyBytes, gasLimit)
const authInfoBytes = makeAuthInfoBytes([pubkey], fee.amount, gasLimit, sequence)
console.log('authinfo: ', authInfoBytes, chainId, accountNumber)
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber)
console.log('signdoc: ', signDoc, signer)
// const { signature, signed } = await signer.signDirect(signerAddress, signDoc)
const signBytes = makeSignBytes(signDoc)
const hashedMessage = sha256(signBytes)
@ -78,7 +74,6 @@ export async function signDirect(signer, signerAddress, messages, fee, memo, { a
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)])
const stdSignature = encodeSecp256k1Signature(pubkey, signatureBytes)
console.log('custom sign:', txBodyBytes, authInfoBytes, signDoc)
return TxRaw.fromPartial({
bodyBytes: txBodyBytes,
authInfoBytes,
@ -104,12 +99,12 @@ export async function sign(device, chainId, signerAddress, messages, fee, memo,
throw new Error('Please install keplr extension')
}
await window.keplr.enable(chainId)
signer = window.getOfflineSignerOnlyAmino(chainId)
signer = window.getOfflineSigner(chainId)
}
// Ensure the address has some tokens to spend
const client = await SigningStargateClient.offline(signer)
return client.signAmino(signerAddress, messages, fee, memo, signerData)
return client.sign(signerAddress, messages, fee, memo, signerData)
// return signDirect(signer, signerAddress, messages, fee, memo, signerData)
}
@ -137,6 +132,19 @@ export function getLocalAccounts() {
return getLocalObject('accounts')
}
export function getLocalTxHistory() {
return getLocalObject('txHistory')
}
export function setLocalTxHistory(newTx) {
const txs = getLocalTxHistory()
if (txs) {
txs.push(newTx)
return localStorage.setItem('txHistory', JSON.stringify(txs))
}
return localStorage.setItem('txHistory', JSON.stringify([newTx]))
}
export function toDuration(value) {
return dayjs.duration(value).humanize()
}

View File

@ -1,174 +1,183 @@
<template>
<b-row class="match-height">
<b-col
v-for="p in proposals"
:key="p.id"
lg="6"
md="12"
>
<b-card>
<b-card-title
class="mb-0"
>
#{{ p.id }}.
<b-badge
v-if="p.status == 1"
pill
variant="light-info"
class="text-right"
<div>
<b-row class="match-height">
<b-col
v-for="p in proposals"
:key="p.id"
lg="6"
md="12"
>
<b-card>
<b-card-title
class="mb-0"
>
Deposit
</b-badge>
<b-badge
v-if="p.status == 2"
pill
variant="light-primary"
class="text-right"
>
Voting
</b-badge>
<b-badge
v-if="p.status == 3"
pill
variant="light-success"
class="text-right"
>
Passed
</b-badge>
<b-badge
v-if="p.status == 4"
pill
variant="light-danger"
class="text-right"
>
Rejected
</b-badge>
<router-link
:to="`./gov/${p.id}`"
>
{{ p.title }}
</router-link></b-card-title>
<b-card-body md="12">
<div class="gov-wrapper d-flex flex-wrap">
<div class="gov">
<p class="card-text mb-25">
Start Date
</p>
<h6 class="mb-0">
{{ p.voting_start_time }}
</h6>
#{{ p.id }}.
<b-badge
v-if="p.status == 1"
pill
variant="light-info"
class="text-right"
>
Deposit
</b-badge>
<b-badge
v-if="p.status == 2"
pill
variant="light-primary"
class="text-right"
>
Voting
</b-badge>
<b-badge
v-if="p.status == 3"
pill
variant="light-success"
class="text-right"
>
Passed
</b-badge>
<b-badge
v-if="p.status == 4"
pill
variant="light-danger"
class="text-right"
>
Rejected
</b-badge>
<router-link
:to="`./gov/${p.id}`"
>
{{ p.title }}
</router-link></b-card-title>
<b-card-body md="12">
<div class="gov-wrapper d-flex flex-wrap">
<div class="gov">
<p class="card-text mb-25">
Start Date
</p>
<h6 class="mb-0">
{{ p.voting_start_time }}
</h6>
</div>
<div class="gov">
<p class="card-text mb-25">
End Date
</p>
<h6 class="mb-0">
{{ p.voting_end_time }}
</h6>
</div>
<div class="gov">
<p class="card-text mb-25">
Deposit
</p>
<h6 class="mb-0">
{{ p.total_deposit || '-' }}
</h6>
</div>
<div class="gov">
<p class="card-text mb-25">
Turnout
</p>
<h6 class="mb-0">
{{ p.tally.turnout }}%
</h6>
</div>
</div>
<div class="gov">
<p class="card-text mb-25">
End Date
</p>
<h6 class="mb-0">
{{ p.voting_end_time }}
</h6>
</div>
<div class="gov">
<p class="card-text mb-25">
Deposit
</p>
<h6 class="mb-0">
{{ p.total_deposit || '-' }}
</h6>
</div>
<div class="gov">
<p class="card-text mb-25">
Turnout
</p>
<h6 class="mb-0">
{{ p.tally.turnout }}%
</h6>
</div>
</div>
</b-card-body>
</b-card-body>
<b-progress
:max="100"
height="2rem"
class="mb-2"
show-progress
>
<b-progress-bar
:id="'vote-yes'+p.id"
variant="success"
:value="p.tally.yes"
<b-progress
:max="100"
height="2rem"
class="mb-2"
show-progress
/>
<b-progress-bar
:id="'vote-no'+p.id"
variant="warning"
:value="p.tally.no"
show-progress
/>
<b-progress-bar
:id="'vote-veto'+p.id"
variant="danger"
:value="p.tally.veto"
show-progress
/>
<b-progress-bar
:id="'vote-abstain'+p.id"
variant="info"
:value="p.tally.abstain"
show-progress
/>
</b-progress>
<b-tooltip
:target="'vote-yes'+p.id"
>
{{ p.tally.yes }}% voted Yes
</b-tooltip>
<b-tooltip
:target="'vote-no'+p.id"
>
{{ p.tally.no }}% voted No
</b-tooltip>
<b-tooltip
:target="'vote-veto'+p.id"
>
{{ p.tally.veto }}% voted No With Veta
</b-tooltip>
<b-tooltip
:target="'vote-abstain'+p.id"
>
{{ p.tally.abstain }}% voted Abstain
</b-tooltip>
<b-card-footer class="pb-0">
<router-link
v-ripple.400="'rgba(113, 102, 240, 0.15)'"
:to="`./gov/${p.id}`"
variant="outline-primary"
>
<b-button
<b-progress-bar
:id="'vote-yes'+p.id"
variant="success"
:value="p.tally.yes"
show-progress
/>
<b-progress-bar
:id="'vote-no'+p.id"
variant="warning"
:value="p.tally.no"
show-progress
/>
<b-progress-bar
:id="'vote-veto'+p.id"
variant="danger"
:value="p.tally.veto"
show-progress
/>
<b-progress-bar
:id="'vote-abstain'+p.id"
variant="info"
:value="p.tally.abstain"
show-progress
/>
</b-progress>
<b-tooltip
:target="'vote-yes'+p.id"
>
{{ p.tally.yes }}% voted Yes
</b-tooltip>
<b-tooltip
:target="'vote-no'+p.id"
>
{{ p.tally.no }}% voted No
</b-tooltip>
<b-tooltip
:target="'vote-veto'+p.id"
>
{{ p.tally.veto }}% voted No With Veta
</b-tooltip>
<b-tooltip
:target="'vote-abstain'+p.id"
>
{{ p.tally.abstain }}% voted Abstain
</b-tooltip>
<b-card-footer class="pb-0">
<router-link
v-ripple.400="'rgba(113, 102, 240, 0.15)'"
:href="`./gov/${p.id}`"
:to="`./gov/${p.id}`"
variant="outline-primary"
>
{{ $t('btn_detail') }}
<b-button
v-ripple.400="'rgba(113, 102, 240, 0.15)'"
:href="`./gov/${p.id}`"
variant="outline-primary"
>
{{ $t('btn_detail') }}
</b-button>
</router-link>
<b-button
v-b-modal.vote-window
:disabled="p.status!=2"
variant="primary"
class="btn float-right mg-2"
@click="selectProposal(p.id, p.title)"
>
{{ $t('btn_vote') }}
</b-button>
</router-link>
<b-button
:disabled="p.status!=2"
variant="primary"
class="btn float-right mg-2"
>
{{ $t('btn_vote') }}
</b-button>
</b-card-footer>
</b-card>
</b-col>
</b-row>
</b-card-footer>
</b-card>
</b-col>
</b-row>
<operation-vote-component
:proposal-id="selectedProposalId"
:title="selectedTitle"
/>
</div>
</template>
<script>
import {
BCard, BCardTitle, BCardBody, BCardFooter, BButton, BProgressBar, BProgress, BBadge, BTooltip, BRow, BCol,
BCard, BCardTitle, BCardBody, BCardFooter, BButton, BProgressBar, BProgress, BBadge, BTooltip, BRow, BCol, VBModal,
} from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import { Proposal } from '@/libs/data'
import OperationVoteComponent from './OperationVoteComponent.vue'
export default {
components: {
@ -183,12 +192,16 @@ export default {
BCardBody,
BRow,
BCol,
OperationVoteComponent,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
selectedProposalId: 0,
selectedTitle: '',
proposals: [new Proposal()],
max: 1,
}
@ -197,6 +210,10 @@ export default {
this.getList()
},
methods: {
selectProposal(pid, title) {
this.selectedProposalId = Number(pid)
this.selectedTitle = title
},
getList() {
this.$http.getGovernanceList().then(res => {
const voting = res.filter(i => i.status === 2)

View File

@ -0,0 +1,470 @@
<template>
<div>
<b-modal
id="delegate-window"
centered
size="md"
title="Delegate Token"
hide-header-close
scrollable
@hidden="resetModal"
@ok="handleOk"
@show="loadBalance"
>
<validation-observer ref="simpleRules">
<b-form>
<b-row>
<b-col>
<b-form-group
label="Delegator"
label-for="Account"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Delegator"
>
<v-select
v-model="selectedAddress"
:options="account"
:reduce="val => val.addr"
label="addr"
placeholder="Select an address"
@change="loadBalance()"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Validator"
label-for="validator"
>
<v-select
v-model="selectedValidator"
:options="valOptions"
:reduce="val => val.value"
placeholder="Select a validator"
/>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Available Token"
label-for="Token"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Token"
>
<v-select
v-model="token"
:options="tokenOptions"
:reduce="token => token.value"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row><b-row>
<b-col>
<b-form-group
label="Amount"
label-for="Amount"
>
<validation-provider
v-slot="{ errors }"
rules="required|regex:^([0-9\.]+)$"
name="amount"
>
<b-form-input
id="Amount"
v-model="amount"
:state="errors.length > 0 ? false:null"
placeholder="Input a number"
type="number"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Fee"
label-for="Fee"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="fee"
>
<b-input-group>
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Memo"
label-for="Memo"
>
<validation-provider
v-slot="{ errors }"
name="memo"
>
<b-form-input
id="Memo"
v-model="memo"
max="2"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Wallet"
label-for="wallet"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="wallet"
>
<b-form-radio-group
v-model="wallet"
stacked
class="demo-inline-spacing"
>
<b-form-radio
v-model="wallet"
name="wallet"
value="keplr"
class="mb-1 mt-1"
>
Keplr
</b-form-radio>
<!-- <b-form-radio
v-model="wallet"
name="wallet"
value="ledgerUSB"
class="mb-1 mt-1"
disabled
>
Ledger (USB)
</b-form-radio>
<b-form-radio
v-model="wallet"
name="wallet"
value="ledgerBle"
class="mb-1 mt-1"
disabled
>
Ledger (Bluetooth)
</b-form-radio> -->
</b-form-radio-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
</b-form>
</validation-observer>
{{ error }}
</b-modal>
</div>
</template>
<script>
import { ValidationProvider, ValidationObserver } from 'vee-validate'
import {
BModal, BRow, BCol, BInputGroup, BFormInput, BFormGroup, BFormSelect, BFormSelectOption,
BForm, BFormRadioGroup, BFormRadio,
} from 'bootstrap-vue'
import {
required, email, url, between, alpha, integer, password, min, digits, alphaDash, length,
} from '@validations'
import {
formatToken, getCachedValidators, getLocalAccounts, getLocalChains, setLocalTxHistory, sign, timeIn,
} from '@/libs/data'
import chainAPI from '@/libs/fetch'
import vSelect from 'vue-select'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
export default {
name: 'DelegateDialogue',
components: {
BModal,
BRow,
BCol,
BForm,
BInputGroup,
BFormInput,
BFormGroup,
BFormSelect,
BFormSelectOption,
BFormRadioGroup,
BFormRadio,
vSelect,
ValidationProvider,
ValidationObserver,
// eslint-disable-next-line vue/no-unused-components
ToastificationContent,
},
props: {
validatorAddress: {
type: String,
default: null,
},
address: {
type: String,
default: null,
},
},
data() {
return {
selectedAddress: this.address,
availableAddress: [],
validators: [],
selectedValidator: null,
token: '',
amount: null,
chainId: '',
selectedChain: '',
balance: [],
delegations: [],
memo: '',
fee: 800,
feeDenom: '',
wallet: 'keplr',
error: null,
sequence: 1,
accountNumber: 0,
required,
password,
email,
min,
integer,
url,
alpha,
between,
digits,
length,
alphaDash,
}
},
computed: {
valOptions() {
return this.validators.map(x => ({ value: x.operator_address, label: x.description.moniker }))
},
tokenOptions() {
if (!this.balance) return []
return this.balance.map(x => ({ value: x.denom, label: formatToken(x) }))
},
feeDenoms() {
if (!this.balance) return []
return this.balance.filter(item => !item.denom.startsWith('ibc'))
},
account() {
return this.computeAccount()
},
},
created() {
// console.log('address: ', this.address)
},
methods: {
computeAccount() {
const accounts = getLocalAccounts()
const chains = getLocalChains()
const values = Object.values(accounts)
let array = []
for (let i = 0; i < values.length; i += 1) {
const addrs = values[i].address.filter(x => x.chain === this.$route.params.chain)
if (addrs && addrs.length > 0) {
array = array.concat(addrs)
if (!this.selectedAddress) {
this.selectedAddress = addrs[0].addr
}
}
const addr = values[i].address.find(x => x.addr === this.selectedAddress)
if (addr) {
this.selectedChain = chains[addr.chain]
}
}
if (!this.selectedChain) {
this.selectedChain = chains[this.$route.params.chain]
}
this.selectedValidator = this.validatorAddress
const reducer = (t, c) => {
if (!(t.find(x => x.addr === c.addr))) t.push(c)
return t
}
return array.reduce(reducer, [])
},
loadBalance() {
if (this.selectedAddress) {
if (!getCachedValidators(this.selectedChain.chain)) {
this.$http.getValidatorList().then(v => {
this.validators = v
})
} else {
this.validators = JSON.parse(getCachedValidators(this.selectedChain.chain))
}
if (this.selectedChain && this.selectedAddress) {
chainAPI.getBankBalance(this.selectedChain.api, this.selectedAddress).then(res => {
if (res && res.length > 0) {
this.balance = res
const token = this.balance.find(i => !i.denom.startsWith('ibc'))
this.token = token.denom
if (token) this.feeDenom = token.denom
}
})
}
this.$http.getLatestBlock(this.selectedChain).then(ret => {
this.chainId = ret.block.header.chain_id
const notSynced = timeIn(ret.block.header.time, 10, 'm')
if (notSynced) {
this.error = 'Client is not synced or blockchain is halted'
} else {
this.error = null
}
})
this.$http.getAuthAccount(this.selectedAddress, this.selectedChain).then(ret => {
if (ret.value.base_vesting_account) {
this.accountNumber = ret.value.base_vesting_account.base_account.account_number
this.sequence = ret.value.base_vesting_account.base_account.sequence
if (!this.sequence) this.sequence = 0
} else {
this.accountNumber = ret.value.account_number
this.sequence = ret.value.sequence ? ret.value.sequence : 0
}
})
this.$http.getStakingDelegations(this.selectedAddress).then(res => {
this.delegations = res.delegation_responses
})
}
},
handleOk(bvModalEvt) {
// console.log('send')
// Prevent modal from closing
bvModalEvt.preventDefault()
// Trigger submit handler
// this.handleSubmit()
this.$refs.simpleRules.validate().then(ok => {
if (ok) {
this.sendTx().then(ret => {
// console.log(ret)
this.error = ret
})
}
})
},
resetModal() {
this.feeDenom = ''
this.error = null
},
format(v) {
return formatToken(v)
},
async sendTx() {
const txMsgs = [{
typeUrl: '/cosmos.staking.v1beta1.MsgDelegate',
value: {
delegatorAddress: this.selectedAddress,
validatorAddress: this.selectedValidator,
amount: {
amount: String((Number(this.amount) * 1000000).toFixed()),
denom: this.token,
},
},
}]
if (txMsgs.length === 0) {
this.error = 'No delegation found'
return ''
}
if (!this.accountNumber) {
this.error = 'Account number should not be empty!'
return ''
}
const txFee = {
amount: [
{
amount: this.fee,
denom: this.feeDenom,
},
],
gas: '200000',
}
const signerData = {
accountNumber: this.accountNumber,
sequence: this.sequence,
chainId: this.chainId,
}
sign(
this.wallet,
this.chainId,
this.address,
txMsgs,
txFee,
this.memo,
signerData,
).then(bodyBytes => {
this.$http.broadcastTx(bodyBytes, this.selectedChain).then(res => {
setLocalTxHistory({ op: 'delegate', hash: res.tx_response.txhash, time: new Date() })
this.$bvModal.hide('delegate-window')
this.$toast({
component: ToastificationContent,
props: {
title: 'Transaction sent!',
icon: 'EditIcon',
variant: 'success',
},
})
}).catch(e => {
this.error = e
})
}).catch(e => {
this.error = e
})
// Send tokens
// return client.sendTokens(this.address, this.recipient, sendCoins, this.memo)
return ''
},
},
}
</script>
<style lang="scss">
@import '@core/scss/vue/libs/vue-select.scss';
</style>

View File

@ -0,0 +1,485 @@
<template>
<div>
<b-modal
id="redelegate-window"
centered
size="md"
title="Redelegate Token"
hide-header-close
scrollable
@hidden="resetModal"
@ok="handleOk"
@show="loadBalance"
>
<validation-observer ref="simpleRules">
<b-form>
<b-row>
<b-col>
<b-form-group
label="Delegator"
label-for="Account"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Delegator"
>
<v-select
v-model="selectedAddress"
:options="account"
:reduce="val => val.addr"
label="addr"
placeholder="Select an address"
@change="loadBalance()"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="From Validator"
label-for="validator"
>
<v-select
v-model="selectedValidator"
:options="valOptions"
:reduce="val => val.value"
placeholder="Select a validator"
/>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="To Validator"
label-for="validator"
>
<v-select
v-model="toValidator"
:options="valOptions"
:reduce="val => val.value"
placeholder="Select a validator"
/>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Staking Token"
label-for="Token"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Token"
>
<v-select
v-model="token"
:options="tokenOptions"
:reduce="token => token.value"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row><b-row>
<b-col>
<b-form-group
label="Amount"
label-for="Amount"
>
<validation-provider
v-slot="{ errors }"
rules="required|regex:^([0-9\.]+)$"
name="amount"
>
<b-form-input
id="Amount"
v-model="amount"
:state="errors.length > 0 ? false:null"
placeholder="Input a number"
type="number"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Fee"
label-for="Fee"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="fee"
>
<b-input-group>
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Memo"
label-for="Memo"
>
<validation-provider
v-slot="{ errors }"
name="memo"
>
<b-form-input
id="Memo"
v-model="memo"
max="2"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Wallet"
label-for="wallet"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="wallet"
>
<b-form-radio-group
v-model="wallet"
stacked
class="demo-inline-spacing"
>
<b-form-radio
v-model="wallet"
name="wallet"
value="keplr"
class="mb-1 mt-1"
>
Keplr
</b-form-radio>
<!-- <b-form-radio
v-model="wallet"
name="wallet"
value="ledgerUSB"
class="mb-1 mt-1"
disabled
>
Ledger (USB)
</b-form-radio>
<b-form-radio
v-model="wallet"
name="wallet"
value="ledgerBle"
class="mb-1 mt-1"
disabled
>
Ledger (Bluetooth)
</b-form-radio> -->
</b-form-radio-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
</b-form>
</validation-observer>
{{ error }}
</b-modal>
</div>
</template>
<script>
import { ValidationProvider, ValidationObserver } from 'vee-validate'
import {
BModal, BRow, BCol, BInputGroup, BFormInput, BFormGroup, BFormSelect, BFormSelectOption,
BForm, BFormRadioGroup, BFormRadio,
} from 'bootstrap-vue'
import {
required, email, url, between, alpha, integer, password, min, digits, alphaDash, length,
} from '@validations'
import {
formatToken, getCachedValidators, getLocalAccounts, getLocalChains, setLocalTxHistory, sign, timeIn,
} from '@/libs/data'
import chainAPI from '@/libs/fetch'
import vSelect from 'vue-select'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
export default {
name: 'UnbondDialogue',
components: {
BModal,
BRow,
BCol,
BForm,
BInputGroup,
BFormInput,
BFormGroup,
BFormSelect,
BFormSelectOption,
BFormRadioGroup,
BFormRadio,
vSelect,
ValidationProvider,
ValidationObserver,
// eslint-disable-next-line vue/no-unused-components
ToastificationContent,
},
props: {
validatorAddress: {
type: String,
default: null,
},
address: {
type: String,
default: null,
},
},
data() {
return {
selectedAddress: this.address,
availableAddress: [],
validators: [],
selectedValidator: this.validatorAddress,
toValidator: null,
token: '',
amount: null,
chainId: '',
selectedChain: '',
balance: [],
delegations: [],
memo: '',
fee: 800,
feeDenom: '',
wallet: 'keplr',
error: null,
sequence: 1,
accountNumber: 0,
required,
password,
email,
min,
integer,
url,
alpha,
between,
digits,
length,
alphaDash,
}
},
computed: {
valOptions() {
return this.validators.map(x => ({ value: x.operator_address, label: x.description.moniker }))
},
tokenOptions() {
if (!this.delegations) return []
return this.delegations.filter(x => x.delegation.validator_address === this.selectedValidator).map(x => ({ value: x.balance.denom, label: formatToken(x.balance) }))
},
feeDenoms() {
if (!this.balance) return []
return this.balance.filter(item => !item.denom.startsWith('ibc'))
},
account() {
return this.computeAccount()
},
},
created() {
// console.log('address: ', this.address)
},
methods: {
computeAccount() {
const accounts = getLocalAccounts()
const chains = getLocalChains()
const values = Object.values(accounts)
let array = []
for (let i = 0; i < values.length; i += 1) {
const addrs = values[i].address.filter(x => x.chain === this.$route.params.chain)
if (addrs && addrs.length > 0) {
array = array.concat(addrs)
if (!this.selectedAddress) {
this.selectedAddress = addrs[0].addr
}
}
const addr = values[i].address.find(x => x.addr === this.selectedAddress)
if (addr) {
this.selectedChain = chains[addr.chain]
}
}
if (!this.selectedChain) {
this.selectedChain = chains[this.$route.params.chain]
}
this.selectedValidator = this.validatorAddress
const reducer = (t, c) => {
if (!(t.find(x => x.addr === c.addr))) t.push(c)
return t
}
return array.reduce(reducer, [])
},
loadBalance() {
if (this.selectedAddress) {
if (!getCachedValidators(this.selectedChain.chain)) {
this.$http.getValidatorList().then(v => {
this.validators = v
})
} else {
this.validators = JSON.parse(getCachedValidators(this.selectedChain.chain))
}
if (this.selectedChain) {
chainAPI.getBankBalance(this.selectedChain.api, this.selectedAddress).then(res => {
if (res && res.length > 0) {
this.balance = res
const token = this.balance.find(i => !i.denom.startsWith('ibc'))
this.token = token.denom
if (token) this.feeDenom = token.denom
}
})
}
this.$http.getLatestBlock(this.selectedChain).then(ret => {
this.chainId = ret.block.header.chain_id
const notSynced = timeIn(ret.block.header.time, 10, 'm')
if (notSynced) {
this.error = 'Client is not synced or blockchain is halted'
} else {
this.error = null
}
})
this.$http.getAuthAccount(this.selectedAddress, this.selectedChain).then(ret => {
if (ret.value.base_vesting_account) {
this.accountNumber = ret.value.base_vesting_account.base_account.account_number
this.sequence = ret.value.base_vesting_account.base_account.sequence
if (!this.sequence) this.sequence = 0
} else {
this.accountNumber = ret.value.account_number
this.sequence = ret.value.sequence ? ret.value.sequence : 0
}
})
}
this.$http.getStakingDelegations(this.selectedAddress).then(res => {
this.delegations = res.delegation_responses
})
},
handleOk(bvModalEvt) {
// console.log('send')
// Prevent modal from closing
bvModalEvt.preventDefault()
this.$refs.simpleRules.validate().then(ok => {
if (ok) {
this.sendTx().then(ret => {
// console.log(ret)
this.error = ret
})
}
})
},
resetModal() {
this.feeDenom = ''
this.error = null
},
format(v) {
return formatToken(v)
},
async sendTx() {
const txMsgs = [{
typeUrl: '/cosmos.staking.v1beta1.MsgBeginRedelegate',
value: {
delegatorAddress: this.selectedAddress,
validatorSrcAddress: this.selectedValidator,
validatorDstAddress: this.toValidator,
amount: {
amount: String((Number(this.amount) * 1000000).toFixed()),
denom: this.token,
},
},
}]
if (txMsgs.length === 0) {
this.error = 'No delegation found'
return ''
}
if (!this.accountNumber) {
this.error = 'Account number should not be empty!'
return ''
}
const txFee = {
amount: [
{
amount: this.fee,
denom: this.feeDenom,
},
],
gas: '250000',
}
const signerData = {
accountNumber: this.accountNumber,
sequence: this.sequence,
chainId: this.chainId,
}
sign(
this.wallet,
this.chainId,
this.address,
txMsgs,
txFee,
this.memo,
signerData,
).then(bodyBytes => {
this.$http.broadcastTx(bodyBytes, this.selectedChain).then(res => {
setLocalTxHistory({ op: 'redelegate', hash: res.tx_response.txhash, time: new Date() })
this.$bvModal.hide('redelegate-window')
this.$toast({
component: ToastificationContent,
props: {
title: 'Transaction sent!',
icon: 'EditIcon',
variant: 'success',
},
})
}).catch(e => {
this.error = e
})
}).catch(e => {
this.error = e
})
// Send tokens
// return client.sendTokens(this.address, this.recipient, sendCoins, this.memo)
return ''
},
},
}
</script>
<style lang="scss">
@import '@core/scss/vue/libs/vue-select.scss';
</style>

View File

@ -134,20 +134,19 @@
name="fee"
>
<b-input-group>
<b-input-group-prepend>
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group-prepend></b-input-group>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
@ -239,7 +238,7 @@ import {
required, email, url, between, alpha, integer, password, min, digits, alphaDash, length,
} from '@validations'
import {
formatToken, getLocalAccounts, getLocalChains, sign, timeIn,
formatToken, getLocalAccounts, getLocalChains, setLocalTxHistory, sign, timeIn,
} from '@/libs/data'
import chainAPI from '@/libs/fetch'
import { Cosmos } from '@cosmostation/cosmosjs'
@ -431,7 +430,7 @@ export default {
).then((bodyBytes, s) => {
console.log('signed: ', bodyBytes, s)
this.$http.broadcastTx(bodyBytes, this.selectedChain).then(res => {
console.log(res)
setLocalTxHistory({ op: 'send', hash: res.txhash, time: new Date() })
this.$bvModal.hide('transfer-window')
this.$toast({
component: ToastificationContent,

View File

@ -0,0 +1,468 @@
<template>
<div>
<b-modal
id="unbond-window"
centered
size="md"
title="Unbond Token"
hide-header-close
scrollable
@hidden="resetModal"
@ok="handleOk"
@show="loadBalance"
>
<validation-observer ref="simpleRules">
<b-form>
<b-row>
<b-col>
<b-form-group
label="Delegator"
label-for="Account"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Delegator"
>
<v-select
v-model="selectedAddress"
:options="account"
:reduce="val => val.addr"
label="addr"
placeholder="Select an address"
@change="loadBalance()"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Validator"
label-for="validator"
>
<v-select
v-model="selectedValidator"
:options="valOptions"
:reduce="val => val.value"
placeholder="Select a validator"
/>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Staking Token"
label-for="Token"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Token"
>
<v-select
v-model="token"
:options="tokenOptions"
:reduce="token => token.value"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row><b-row>
<b-col>
<b-form-group
label="Amount"
label-for="Amount"
>
<validation-provider
v-slot="{ errors }"
rules="required|regex:^([0-9\.]+)$"
name="amount"
>
<b-form-input
id="Amount"
v-model="amount"
:state="errors.length > 0 ? false:null"
placeholder="Input a number"
type="number"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Fee"
label-for="Fee"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="fee"
>
<b-input-group>
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Memo"
label-for="Memo"
>
<validation-provider
v-slot="{ errors }"
name="memo"
>
<b-form-input
id="Memo"
v-model="memo"
max="2"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Wallet"
label-for="wallet"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="wallet"
>
<b-form-radio-group
v-model="wallet"
stacked
class="demo-inline-spacing"
>
<b-form-radio
v-model="wallet"
name="wallet"
value="keplr"
class="mb-1 mt-1"
>
Keplr
</b-form-radio>
<!-- <b-form-radio
v-model="wallet"
name="wallet"
value="ledgerUSB"
class="mb-1 mt-1"
disabled
>
Ledger (USB)
</b-form-radio>
<b-form-radio
v-model="wallet"
name="wallet"
value="ledgerBle"
class="mb-1 mt-1"
disabled
>
Ledger (Bluetooth)
</b-form-radio> -->
</b-form-radio-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
</b-form>
</validation-observer>
{{ error }}
</b-modal>
</div>
</template>
<script>
import { ValidationProvider, ValidationObserver } from 'vee-validate'
import {
BModal, BRow, BCol, BInputGroup, BFormInput, BFormGroup, BFormSelect, BFormSelectOption,
BForm, BFormRadioGroup, BFormRadio,
} from 'bootstrap-vue'
import {
required, email, url, between, alpha, integer, password, min, digits, alphaDash, length,
} from '@validations'
import {
formatToken, getCachedValidators, getLocalAccounts, getLocalChains, setLocalTxHistory, sign, timeIn,
} from '@/libs/data'
import chainAPI from '@/libs/fetch'
import vSelect from 'vue-select'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
export default {
name: 'UnbondDialogue',
components: {
BModal,
BRow,
BCol,
BForm,
BInputGroup,
BFormInput,
BFormGroup,
BFormSelect,
BFormSelectOption,
BFormRadioGroup,
BFormRadio,
vSelect,
ValidationProvider,
ValidationObserver,
// eslint-disable-next-line vue/no-unused-components
ToastificationContent,
},
props: {
validatorAddress: {
type: String,
default: null,
},
address: {
type: String,
default: null,
},
},
data() {
return {
selectedAddress: this.address,
availableAddress: [],
validators: [],
selectedValidator: this.validatorAddress,
token: '',
amount: null,
chainId: '',
selectedChain: '',
balance: [],
delegations: [],
memo: '',
fee: 800,
feeDenom: '',
wallet: 'keplr',
error: null,
sequence: 1,
accountNumber: 0,
required,
password,
email,
min,
integer,
url,
alpha,
between,
digits,
length,
alphaDash,
}
},
computed: {
valOptions() {
return this.validators.map(x => ({ value: x.operator_address, label: x.description.moniker }))
},
tokenOptions() {
if (!this.delegations) return []
return this.delegations.filter(x => x.delegation.validator_address === this.selectedValidator).map(x => ({ value: x.balance.denom, label: formatToken(x.balance) }))
},
feeDenoms() {
if (!this.balance) return []
return this.balance.filter(item => !item.denom.startsWith('ibc'))
},
account() {
return this.computeAccount()
},
},
created() {
// console.log('address: ', this.address)
},
methods: {
computeAccount() {
const accounts = getLocalAccounts()
const chains = getLocalChains()
const values = Object.values(accounts)
let array = []
for (let i = 0; i < values.length; i += 1) {
const addrs = values[i].address.filter(x => x.chain === this.$route.params.chain)
if (addrs && addrs.length > 0) {
array = array.concat(addrs)
if (!this.selectedAddress) {
this.selectedAddress = addrs[0].addr
}
}
const addr = values[i].address.find(x => x.addr === this.selectedAddress)
if (addr) {
this.selectedChain = chains[addr.chain]
}
}
if (!this.selectedChain) {
this.selectedChain = chains[this.$route.params.chain]
}
this.selectedValidator = this.validatorAddress
const reducer = (t, c) => {
if (!(t.find(x => x.addr === c.addr))) t.push(c)
return t
}
return array.reduce(reducer, [])
},
loadBalance() {
if (this.selectedAddress) {
if (!getCachedValidators(this.selectedChain.chain)) {
this.$http.getValidatorList().then(v => {
this.validators = v
})
} else {
this.validators = JSON.parse(getCachedValidators(this.selectedChain.chain))
}
if (this.selectedChain) {
chainAPI.getBankBalance(this.selectedChain.api, this.selectedAddress).then(res => {
if (res && res.length > 0) {
this.balance = res
const token = this.balance.find(i => !i.denom.startsWith('ibc'))
this.token = token.denom
if (token) this.feeDenom = token.denom
}
})
}
this.$http.getLatestBlock(this.selectedChain).then(ret => {
this.chainId = ret.block.header.chain_id
const notSynced = timeIn(ret.block.header.time, 10, 'm')
if (notSynced) {
this.error = 'Client is not synced or blockchain is halted'
} else {
this.error = null
}
})
this.$http.getAuthAccount(this.selectedAddress, this.selectedChain).then(ret => {
if (ret.value.base_vesting_account) {
this.accountNumber = ret.value.base_vesting_account.base_account.account_number
this.sequence = ret.value.base_vesting_account.base_account.sequence
if (!this.sequence) this.sequence = 0
} else {
this.accountNumber = ret.value.account_number
this.sequence = ret.value.sequence ? ret.value.sequence : 0
}
})
}
this.$http.getStakingDelegations(this.selectedAddress).then(res => {
this.delegations = res.delegation_responses
})
},
handleOk(bvModalEvt) {
// console.log('send')
// Prevent modal from closing
bvModalEvt.preventDefault()
this.$refs.simpleRules.validate().then(ok => {
if (ok) {
this.sendTx().then(ret => {
// console.log(ret)
this.error = ret
})
}
})
},
resetModal() {
this.feeDenom = ''
this.error = null
},
format(v) {
return formatToken(v)
},
async sendTx() {
const txMsgs = [{
typeUrl: '/cosmos.staking.v1beta1.MsgUndelegate',
value: {
delegatorAddress: this.selectedAddress,
validatorAddress: this.selectedValidator,
amount: {
amount: String((Number(this.amount) * 1000000).toFixed()),
denom: this.token,
},
},
}]
if (txMsgs.length === 0) {
this.error = 'No delegation found'
return ''
}
if (!this.accountNumber) {
this.error = 'Account number should not be empty!'
return ''
}
const txFee = {
amount: [
{
amount: this.fee,
denom: this.feeDenom,
},
],
gas: '200000',
}
const signerData = {
accountNumber: this.accountNumber,
sequence: this.sequence,
chainId: this.chainId,
}
sign(
this.wallet,
this.chainId,
this.address,
txMsgs,
txFee,
this.memo,
signerData,
).then(bodyBytes => {
this.$http.broadcastTx(bodyBytes, this.selectedChain).then(res => {
setLocalTxHistory({ op: 'unbond', hash: res.tx_response.txhash, time: new Date() })
this.$bvModal.hide('unbond-window')
this.$toast({
component: ToastificationContent,
props: {
title: 'Transaction sent!',
icon: 'EditIcon',
variant: 'success',
},
})
}).catch(e => {
this.error = e
})
}).catch(e => {
this.error = e
})
// Send tokens
// return client.sendTokens(this.address, this.recipient, sendCoins, this.memo)
return ''
},
},
}
</script>
<style lang="scss">
@import '@core/scss/vue/libs/vue-select.scss';
</style>

View File

@ -0,0 +1,431 @@
<template>
<div>
<b-modal
id="vote-window"
centered
size="md"
title="Vote"
hide-header-close
scrollable
@hidden="resetModal"
@ok="handleOk"
@show="loadBalance"
>
<validation-observer ref="simpleRules">
<b-form>
<b-row>
<b-col>
<h4>{{ proposalId }}. {{ title }}</h4>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Voter"
label-for="Account"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Voter"
>
<v-select
v-model="voter"
:options="account"
:reduce="val => val.addr"
label="addr"
placeholder="Select an address"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Option"
label-for="option"
>
<div class="demo-inline-spacing">
<b-form-radio
v-model="option"
name="option"
value="1"
class="custom-control-success"
>
Yes
</b-form-radio>
<b-form-radio
v-model="option"
name="option"
value="3"
class="custom-control-warning"
>
No
</b-form-radio>
<b-form-radio
v-model="option"
name="option"
value="4"
class="custom-control-danger"
>
No with Veto
</b-form-radio>
<b-form-radio
v-model="option"
name="option"
value="2"
class="custom-control-secondary"
>
Abstain
</b-form-radio>
</div>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Fee"
label-for="Fee"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="fee"
>
<b-input-group>
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Memo"
label-for="Memo"
>
<validation-provider
v-slot="{ errors }"
name="memo"
>
<b-form-input
id="Memo"
v-model="memo"
max="2"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Wallet"
label-for="wallet"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="wallet"
>
<b-form-radio-group
v-model="wallet"
stacked
class="demo-inline-spacing"
>
<b-form-radio
v-model="wallet"
name="wallet"
value="keplr"
class="mb-1 mt-1"
>
Keplr
</b-form-radio>
<!-- <b-form-radio
v-model="wallet"
name="wallet"
value="ledgerUSB"
class="mb-1 mt-1"
disabled
>
Ledger (USB)
</b-form-radio>
<b-form-radio
v-model="wallet"
name="wallet"
value="ledgerBle"
class="mb-1 mt-1"
disabled
>
Ledger (Bluetooth)
</b-form-radio> -->
</b-form-radio-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
</b-form>
</validation-observer>
{{ error }}
</b-modal>
</div>
</template>
<script>
import { ValidationProvider, ValidationObserver } from 'vee-validate'
import {
BModal, BRow, BCol, BInputGroup, BFormInput, BFormGroup, BFormSelect, BFormSelectOption,
BForm, BFormRadioGroup, BFormRadio,
} from 'bootstrap-vue'
import {
required, email, url, between, alpha, integer, password, min, digits, alphaDash, length,
} from '@validations'
import {
formatToken, getLocalAccounts, getLocalChains, setLocalTxHistory, sign, timeIn,
} from '@/libs/data'
import chainAPI from '@/libs/fetch'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
import vSelect from 'vue-select'
export default {
name: 'VoteDialogue',
components: {
BModal,
BRow,
BCol,
BForm,
BInputGroup,
BFormInput,
BFormGroup,
BFormSelect,
BFormSelectOption,
BFormRadioGroup,
BFormRadio,
vSelect,
ValidationProvider,
ValidationObserver,
// eslint-disable-next-line vue/no-unused-components
ToastificationContent,
},
props: {
proposalId: {
type: Number,
required: true,
},
title: {
type: String,
required: true,
},
},
data() {
return {
voter: null,
option: null,
chainId: '',
selectedChain: '',
balance: [],
memo: '',
fee: 800,
feeDenom: '',
wallet: 'keplr',
error: null,
sequence: 1,
accountNumber: 0,
required,
password,
email,
min,
integer,
url,
alpha,
between,
digits,
length,
alphaDash,
}
},
computed: {
feeDenoms() {
return this.balance.filter(item => !item.denom.startsWith('ibc'))
},
account() {
// if (accounts && accounts[this.name]) {
// const config = accounts[this.name]
// const addr = config.address.find(x => x.addr === this.address)
// if (addr) return addr
// }
return this.computeAccount()
},
},
methods: {
computeAccount() {
const accounts = getLocalAccounts()
const chains = getLocalChains()
const values = Object.values(accounts)
let array = []
for (let i = 0; i < values.length; i += 1) {
const addrs = values[i].address.filter(x => x.chain === this.$route.params.chain)
if (addrs && addrs.length > 0) {
array = array.concat(addrs)
if (!this.voter) {
this.voter = addrs[0].addr
}
}
const addr = values[i].address.find(x => x.addr === this.selectedAddress)
if (addr) {
this.selectedChain = chains[addr.chain]
}
}
if (!this.selectedChain) {
this.selectedChain = chains[this.$route.params.chain]
}
const reducer = (t, c) => {
if (!(t.find(x => x.addr === c.addr))) t.push(c)
return t
}
return array.reduce(reducer, [])
},
loadBalance() {
if (this.voter) {
chainAPI.getBankBalance(this.selectedChain.api, this.voter).then(res => {
if (res && res.length > 0) {
this.balance = res
const token = this.balance.find(i => !i.denom.startsWith('ibc'))
if (token) this.feeDenom = token.denom
}
})
this.$http.getLatestBlock(this.selectedChain).then(ret => {
this.chainId = ret.block.header.chain_id
const notSynced = timeIn(ret.block.header.time, 10, 'm')
if (notSynced) {
this.error = 'Client is not synced or blockchain is halted'
} else {
this.error = null
}
})
this.$http.getAuthAccount(this.voter, this.selectedChain).then(ret => {
if (ret.value.base_vesting_account) {
this.accountNumber = ret.value.base_vesting_account.base_account.account_number
this.sequence = ret.value.base_vesting_account.base_account.sequence
if (!this.sequence) this.sequence = 0
} else {
this.accountNumber = ret.value.account_number
this.sequence = ret.value.sequence ? ret.value.sequence : 0
}
})
}
},
handleOk(bvModalEvt) {
// console.log('send')
// Prevent modal from closing
bvModalEvt.preventDefault()
this.$refs.simpleRules.validate().then(ok => {
if (ok) {
this.sendTx().then(ret => {
// console.log(ret)
this.error = ret
})
}
})
},
resetModal() {
this.feeDenom = ''
this.error = null
},
format(v) {
return formatToken(v)
},
async sendTx() {
const txMsgs = [{
typeUrl: '/cosmos.gov.v1beta1.MsgVote',
value: {
voter: this.voter,
proposalId: this.proposalId,
option: this.option,
},
}]
if (txMsgs.length === 0) {
this.error = 'No delegation found'
return ''
}
if (!this.accountNumber) {
this.error = 'Account number should not be empty!'
return ''
}
const txFee = {
amount: [
{
amount: this.fee,
denom: this.feeDenom,
},
],
gas: '200000',
}
const signerData = {
accountNumber: this.accountNumber,
sequence: this.sequence,
chainId: this.chainId,
}
sign(
this.wallet,
this.chainId,
this.voter,
txMsgs,
txFee,
this.memo,
signerData,
).then(bodyBytes => {
this.$http.broadcastTx(bodyBytes, this.selectedChain).then(res => {
setLocalTxHistory({ op: 'vote', hash: res.tx_response.txhash, time: new Date() })
this.$bvModal.hide('vote-window')
this.$toast({
component: ToastificationContent,
props: {
title: 'Transaction sent!',
icon: 'EditIcon',
variant: 'success',
},
})
}).catch(e => {
this.error = e
})
}).catch(e => {
this.error = e
})
// Send tokens
// return client.sendTokens(this.address, this.recipient, sendCoins, this.memo)
return ''
},
},
}
</script>
<style lang="scss">
@import '@core/scss/vue/libs/vue-select.scss';
</style>

View File

@ -48,20 +48,19 @@
name="fee"
>
<b-input-group>
<b-input-group-prepend>
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
<b-form-input v-model="fee" />
<b-form-select
v-model="feeDenom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
<b-form-select-option
v-for="item in feeDenoms"
:key="item.denom"
:value="item.denom"
>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group-prepend></b-input-group>
{{ item.denom }}
</b-form-select-option>
</b-form-select>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>

View File

@ -18,73 +18,74 @@
<span>Validators {{ validators.length }}/{{ stakingParameters.max_validators }} </span>
</b-card-title>
</b-card-header>
<b-table
:items="validators"
:fields="validator_fields"
:sort-desc="true"
sort-by="tokens"
striped
hover
responsive="sm"
class="text-nowrap"
>
<!-- A virtual column -->
<template #cell(index)="data">
<b-badge
:variant="rankBadge(data)"
>
{{ data.index + 1 }}
</b-badge>
</template>
<!-- Column: Validator -->
<template #cell(description)="data">
<b-media vertical-align="center d-none d-md-block">
<template #aside>
<b-avatar
v-if="data.item.avatar"
v-b-tooltip.hover.v-primary
v-b-tooltip.hover.right="data.item.description.details"
size="32"
variant="light-primary"
:src="data.item.avatar"
/>
<b-avatar
v-if="!data.item.avatar"
v-b-tooltip.hover.v-primary
v-b-tooltip.hover.right="data.item.description.details"
>
<feather-icon icon="ServerIcon" />
</b-avatar>
</template>
<span class="font-weight-bolder d-block text-nowrap">
<router-link
:to="`./staking/${data.item.operator_address}`"
>
{{ data.item.description.moniker }}
</router-link>
</span>
<small class="text-muted">{{ data.item.description.website || data.item.description.identity }}</small>
</b-media>
</template>
<!-- Token -->
<template #cell(tokens)="data">
<div
v-if="data.item.tokens > 0"
class="d-flex flex-column"
>
<span class="font-weight-bold mb-0">{{ tokenFormatter(data.item.tokens, stakingParameters.bond_denom) }}</span>
<span class="font-small-2 text-muted text-nowrap">{{ percent(data.item.tokens/stakingPool) }}%</span>
</div>
<span v-else>{{ data.item.delegator_shares }}</span>
</template>
</b-table>
<b-card-body class="pl-0 pr-0">
<b-table
:items="validators"
:fields="validator_fields"
:sort-desc="true"
sort-by="tokens"
striped
hover
responsive="sm"
>
<!-- A virtual column -->
<template #cell(index)="data">
<b-badge
:variant="rankBadge(data)"
>
{{ data.index + 1 }}
</b-badge>
</template>
<!-- Column: Validator -->
<template #cell(description)="data">
<b-media vertical-align="center d-none d-md-block">
<template #aside>
<b-avatar
v-if="data.item.avatar"
v-b-tooltip.hover.v-primary
v-b-tooltip.hover.right="data.item.description.details"
size="32"
variant="light-primary"
:src="data.item.avatar"
/>
<b-avatar
v-if="!data.item.avatar"
v-b-tooltip.hover.v-primary
v-b-tooltip.hover.right="data.item.description.details"
>
<feather-icon icon="ServerIcon" />
</b-avatar>
</template>
<span class="font-weight-bolder d-block text-nowrap">
<router-link
:to="`./staking/${data.item.operator_address}`"
>
{{ data.item.description.moniker }}
</router-link>
</span>
<small class="text-muted">{{ data.item.description.website || data.item.description.identity }}</small>
</b-media>
</template>
<!-- Token -->
<template #cell(tokens)="data">
<div
v-if="data.item.tokens > 0"
class="d-flex flex-column"
>
<span class="font-weight-bold mb-0">{{ tokenFormatter(data.item.tokens, stakingParameters.bond_denom) }}</span>
<span class="font-small-2 text-muted text-nowrap d-none d-lg-block">{{ percent(data.item.tokens/stakingPool) }}%</span>
</div>
<span v-else>{{ data.item.delegator_shares }}</span>
</template>
</b-table>
</b-card-body>
</b-card>
</div>
</template>
<script>
import {
BTable, BMedia, BAvatar, BBadge, BCard, BCardHeader, BCardTitle, VBTooltip,
BTable, BMedia, BAvatar, BBadge, BCard, BCardHeader, BCardTitle, VBTooltip, BCardBody,
} from 'bootstrap-vue'
import {
Validator, percent, StakingParameters, formatToken,
@ -101,6 +102,7 @@ export default {
BBadge,
BCardHeader,
BCardTitle,
BCardBody,
},
directives: {
'b-tooltip': VBTooltip,

View File

@ -25,18 +25,29 @@
</div>
<div class="d-flex flex-wrap">
<b-button
v-b-modal.delegate-window
size="sm"
variant="primary"
class="mr-25 mb-25"
>
Delegate
</b-button>
<b-button
v-b-modal.redelegate-window
size="sm"
variant="outline-danger"
class="ml-1"
class="mr-25 mb-25"
>
Redelegate
</b-button>
<b-button
v-b-modal.unbond-window
size="sm"
variant="outline-danger"
class="mr-25 mb-25"
>
Unbond
</b-button>
</div>
</div>
</div>
@ -62,24 +73,6 @@
</div>
<div class="d-flex align-items-center mr-2">
<b-avatar
variant="light-success"
rounded
>
<feather-icon
icon="TrendingUpIcon"
size="18"
/>
</b-avatar>
<div class="ml-1">
<h5 class="mb-0">
{{ apr(validator.commission.rate) }}
</h5>
<small>Annual Profit</small>
</div>
</div>
<div class="d-flex align-items-center">
<b-avatar
variant="light-warning"
rounded
@ -96,6 +89,27 @@
<small>Self Delegation</small>
</div>
</div>
<div
v-if="mintInflation"
class="d-flex align-items-center"
>
<b-avatar
variant="light-success"
rounded
>
<feather-icon
icon="TrendingUpIcon"
size="18"
/>
</b-avatar>
<div class="ml-1">
<h5 class="mb-0">
{{ apr(validator.commission.rate) }}
</h5>
<small>Annual Profit</small>
</div>
</div>
</div>
</b-col>
@ -195,7 +209,7 @@
<b-card-footer
v-if="validator.description.details"
class="mt-1"
class="mt-1 pl-0 pr-0"
>
{{ validator.description.details || '' }}
</b-card-footer>
@ -255,12 +269,15 @@
</b-col>
</b-row>
</template>
<operation-delegate-component :validator-address="validator.operator_address" />
<operation-redelegate-component :validator-address="validator.operator_address" />
<operation-unbond-component :validator-address="validator.operator_address" />
</div>
</template>
<script>
import {
BCard, BButton, BAvatar, BRow, BCol, BCardBody, BCardFooter, VBTooltip,
BCard, BButton, BAvatar, BRow, BCol, BCardBody, BCardFooter, VBTooltip, VBModal,
} from 'bootstrap-vue'
import {
@ -270,6 +287,9 @@ import { keybase } from '@/libs/fetch'
import StakingAddressComponent from './StakingAddressComponent.vue'
import StakingCommissionComponent from './StakingCommissionComponent.vue'
import StakingRewardComponent from './StakingRewardComponent.vue'
import OperationDelegateComponent from './OperationDelegateComponent.vue'
import OperationRedelegateComponent from './OperationRedelegateComponent.vue'
import OperationUnbondComponent from './OperationUnbondComponent.vue'
export default {
components: {
@ -283,8 +303,12 @@ export default {
StakingAddressComponent,
StakingCommissionComponent,
StakingRewardComponent,
OperationDelegateComponent,
OperationRedelegateComponent,
OperationUnbondComponent,
},
directives: {
'b-modal': VBModal,
'b-tooltip': VBTooltip,
},
data() {

View File

@ -103,19 +103,65 @@
<b-card>
<b-card-header class="pt-0 pl-0 pr-0">
<b-card-title>Delegation</b-card-title>
<b-button
v-b-modal.withdraw-window
variant="primary"
size="sm"
>
Withdraw
</b-button>
<div>
<b-button
v-b-modal.delegate-window
variant="primary"
size="sm"
class="mr-25"
>
Delegate
</b-button>
<b-button
v-if="delegations"
v-b-modal.withdraw-window
variant="primary"
size="sm"
>
Withdraw
</b-button>
</div>
</b-card-header>
<b-card-body class="pl-0 pr-0">
<b-table
:items="deleTable"
stacked="sm"
/>
>
<template #cell(action)="data">
<!-- size -->
<b-button-group
size="sm"
>
<b-button
v-b-modal.delegate-window
v-ripple.400="'rgba(113, 102, 240, 0.15)'"
v-b-tooltip.hover.top="'Delegate'"
variant="outline-primary"
@click="selectValue(data.value)"
>
<feather-icon icon="LogInIcon" />
</b-button>
<b-button
v-b-modal.redelegate-window
v-ripple.400="'rgba(113, 102, 240, 0.15)'"
v-b-tooltip.hover.top="'Redelegate'"
variant="outline-primary"
@click="selectValue(data.value)"
>
<feather-icon icon="ShuffleIcon" />
</b-button>
<b-button
v-b-modal.unbond-window
v-ripple.400="'rgba(113, 102, 240, 0.15)'"
v-b-tooltip.hover.top="'Unbond'"
variant="outline-primary"
@click="selectValue(data.value)"
>
<feather-icon icon="LogOutIcon" />
</b-button>
</b-button-group>
</template>
</b-table>
</b-card-body>
</b-card>
@ -216,12 +262,25 @@
<operation-transfer-component :address="address" />
<operation-withdraw-component :address="address" />
<operation-unbond-component
:address="address"
:validator-address.sync="selectedValidator"
/>
<operation-delegate-component
:address="address"
:validator-address.sync="selectedValidator"
/>
<operation-redelegate-component
:address="address"
:validator-address.sync="selectedValidator"
/>
</div>
</template>
<script>
import {
BCard, BAvatar, BPopover, BTable, BRow, BCol, BTableSimple, BTr, BTd, BTbody, BCardHeader, BCardTitle, BButton, BCardBody, VBModal,
BButtonGroup, VBTooltip,
} from 'bootstrap-vue'
import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
@ -236,6 +295,9 @@ import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ChartjsComponentDoughnutChart from './ChartjsComponentDoughnutChart.vue'
import OperationTransferComponent from './OperationTransferComponent.vue'
import OperationWithdrawComponent from './OperationWithdrawComponent.vue'
import OperationUnbondComponent from './OperationUnbondComponent.vue'
import OperationDelegateComponent from './OperationDelegateComponent.vue'
import OperationRedelegateComponent from './OperationRedelegateComponent.vue'
export default {
components: {
@ -253,6 +315,7 @@ export default {
BCardTitle,
BCardBody,
BButton,
BButtonGroup,
BTr,
BTd,
// eslint-disable-next-line vue/no-unused-components
@ -261,14 +324,19 @@ export default {
ChartjsComponentDoughnutChart,
OperationTransferComponent,
OperationWithdrawComponent,
OperationDelegateComponent,
OperationRedelegateComponent,
OperationUnbondComponent,
},
directives: {
'b-modal': VBModal,
'b-tooltip': VBTooltip,
Ripple,
},
data() {
const { address } = this.$route.params
return {
selectedValidator: '',
totalCurrency: 0,
address,
account: null,
@ -336,16 +404,26 @@ export default {
return xh
}))
total = total.concat(this.delegations.map(x => {
const xh = x.balance
xh.type = 'Delegation'
xh.color = 'success'
xh.icon = 'LockIcon'
xh.currency = this.formatCurrency(xh.amount, xh.denom)
sumCurrency += xh.currency
sum += Number(xh.amount)
return xh
}))
let stakingDenom = ''
if (this.delegations) {
let temp = 0
this.delegations.forEach(x => {
const xh = x.balance
temp += Number(xh.amount)
sumCurrency += this.formatCurrency(xh.amount, xh.denom)
sum += Number(xh.amount)
stakingDenom = xh.denom
})
total.push({
type: 'Delegation',
color: 'success',
icon: 'LockIcon',
amount: temp,
denom: stakingDenom,
currency: this.formatCurrency(temp, stakingDenom),
})
}
if (this.reward.total) {
total = total.concat(this.reward.total.map(x => {
@ -367,12 +445,23 @@ export default {
// xh.icon = 'TrendingUpIcon'
// return xh
// }))
let tmp1 = 0
this.unbonding.forEach(x => {
x.entries.forEach(e => {
tmp1 += Number(e.balance)
})
})
this.redelegations.forEach(x => {
x.entries.forEach(e => {
tmp1 += Number(e.balance)
})
})
total.push({
type: 'unbonding',
color: 'danger',
icon: 'TrendingDownIcon',
denom: '',
amount: 0,
denom: stakingDenom,
amount: tmp1,
percent: 0,
currency: 0,
})
@ -411,13 +500,14 @@ export default {
},
deleTable() {
const re = []
if (this.reward.rewards) {
if (this.reward.rewards && this.delegations) {
this.delegations.forEach(e => {
const reward = this.reward.rewards.find(r => r.validator_address === e.delegation.validator_address)
re.push({
validator: getStakingValidatorOperator(this.$http.config.chain_name, e.delegation.validator_address, 8),
token: formatToken(e.balance),
reward: tokenFormatter(reward.reward),
action: e.delegation.validator_address,
})
})
}
@ -475,6 +565,9 @@ export default {
// })
},
methods: {
selectValue(v) {
this.selectedValidator = v
},
formatDenom(v) {
return formatTokenDenom(this.denoms[v] ? this.denoms[v] : v)
},

View File

@ -223,7 +223,6 @@ export default {
},
transfer(addr) {
this.selectedAddress = addr
console.log(this.selectedAddress)
},
completeAdd() {
this.init()