Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 187 additions & 2 deletions src/components/BpVotes.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<script setup lang="ts">
import { ref, defineProps, onBeforeMount, watch } from 'vue';
import { ref, defineProps, onBeforeMount, watch, computed } from 'vue';
import { getHyperionAccountData } from 'src/api/hyperion';
import { api } from 'src/api';
import { GetTableRowsParams } from 'src/types';
import { getChain } from 'src/config/ConfigManager';
import { Name } from '@wharfkit/session';
import { formatCurrency } from 'src/utils/string-utils';

const chain = getChain();
const symbol = chain.getSystemToken().symbol;

const props = defineProps({
account: {
Expand All @@ -10,6 +18,15 @@ const props = defineProps({
});

const bpVotes = ref([]);
const isProducer = ref(false);
const isSelfStaking = ref(false);
const hasBoostActive = ref(false);
const selfStakedAmount = ref(0);
const selfStakeBoostVotes = ref(0);
const totalVotes = ref(0);
const producerRank = ref(0);
const numProducersVoted = ref(0);

const fetchVotes = async () => {
try {
const {
Expand All @@ -22,10 +39,76 @@ const fetchVotes = async () => {
console.error(e);
}
};
onBeforeMount(fetchVotes);

const fetchSelfStakeInfo = async () => {
try {
// Check if this account is a registered producer
const producerResult = await api.getTableRows({
code: 'eosio',
scope: 'eosio',
table: 'producers',
lower_bound: Name.from(props.account),
limit: 1,
}) as { rows: { owner: string; is_active: number; total_votes: string }[] };

if (producerResult.rows.length > 0 && producerResult.rows[0].owner === props.account && producerResult.rows[0].is_active === 1) {
isProducer.value = true;
totalVotes.value = parseFloat(producerResult.rows[0].total_votes);

// Get rank
const allProducers = (await api.getProducers()).rows;
const activeProducers = allProducers
.filter(p => p.is_active === 1)
.sort((a, b) => b.total_votes - a.total_votes);
const rankIndex = activeProducers.findIndex(p => p.owner === props.account);
producerRank.value = rankIndex >= 0 ? rankIndex + 1 : 0;
} else {
isProducer.value = false;
return;
}

// Check voter info for self-stake
const voterResult = await api.getTableRows({
code: 'eosio',
scope: 'eosio',
table: 'voters',
lower_bound: Name.from(props.account),
limit: 1,
}) as { rows: { owner: string; staked: number; self_stake_boost: number; producers: string[]; last_vote_weight: number }[] };

if (voterResult.rows.length > 0 && voterResult.rows[0].owner === props.account) {
const voter = voterResult.rows[0];
selfStakedAmount.value = voter.staked;
numProducersVoted.value = voter.producers.length;
isSelfStaking.value = voter.producers.includes(props.account);
hasBoostActive.value = voter.self_stake_boost > 0 && isSelfStaking.value;
if (hasBoostActive.value) {
selfStakeBoostVotes.value = 10 * voter.last_vote_weight;
}
}
} catch(e) {
console.error(e);
}
};

const boostStatus = computed(() => {
if (!isProducer.value) return 'not-producer';
if (hasBoostActive.value) return 'active';
if (isSelfStaking.value) return 'needs-activation';
return 'not-self-staking';
});

onBeforeMount(async () => {
await fetchVotes();
await fetchSelfStakeInfo();
});
watch(() => props.account, async () => {
bpVotes.value = [];
isProducer.value = false;
isSelfStaking.value = false;
hasBoostActive.value = false;
await fetchVotes();
await fetchSelfStakeInfo();
});
</script>

Expand All @@ -34,6 +117,90 @@ watch(() => props.account, async () => {
class="row col-12 q-my-xs justify-center text-left container-max-width"
>
<div class="row col-11">
<!-- Self-Stake Boost Section (for registered producers) -->
<div v-if="isProducer" class="row col-12 q-mt-lg">
<div class="col-12">
<p class="panel-title">Block Producer Self-Stake</p>
</div>
<q-separator class="row col-12 q-mt-md q-mb-md separator" />

<div class="col-12 q-pa-md">
<div class="row q-col-gutter-md">
<!-- Rank & Votes -->
<div class="col-12 col-sm-4">
<div class="self-stake-stat">
<div class="self-stake-label">Producer Rank</div>
<div class="self-stake-value">#{{ producerRank }}</div>
</div>
</div>
<div class="col-12 col-sm-4">
<div class="self-stake-stat">
<div class="self-stake-label">Total Votes</div>
<div class="self-stake-value">{{ (totalVotes / 10000).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0}) }}</div>
</div>
</div>
<div class="col-12 col-sm-4">
<div class="self-stake-stat">
<div class="self-stake-label">Voting For</div>
<div class="self-stake-value">{{ numProducersVoted }} / 30 BPs</div>
</div>
</div>

<!-- Self-Stake Info -->
<div class="col-12 col-sm-4">
<div class="self-stake-stat">
<div class="self-stake-label">Self-Staked</div>
<div class="self-stake-value">{{ (selfStakedAmount / 10000).toLocaleString(undefined, {minimumFractionDigits: 4, maximumFractionDigits: 4}) }} {{ symbol }}</div>
</div>
</div>
<div class="col-12 col-sm-4">
<div class="self-stake-stat">
<div class="self-stake-label">10x Boost Status</div>
<div class="self-stake-value">
<q-chip
v-if="boostStatus === 'active'"
color="primary"
text-color="white"
label="Active"
size="sm"
icon="verified"
/>
<q-chip
v-else-if="boostStatus === 'needs-activation'"
outline
color="warning"
text-color="warning"
label="Needs Re-Vote"
size="sm"
icon="info"
>
<q-tooltip>Self-stakes but boost not activated. Re-vote or change stake to activate the 10x multiplier.</q-tooltip>
</q-chip>
<q-chip
v-else-if="boostStatus === 'not-self-staking'"
outline
color="negative"
text-color="negative"
label="Not Self-Staking"
size="sm"
icon="warning"
>
<q-tooltip>This producer does not vote for itself. Self-staking enables a 10x vote weight boost.</q-tooltip>
</q-chip>
</div>
</div>
</div>
<div v-if="hasBoostActive" class="col-12 col-sm-4">
<div class="self-stake-stat">
<div class="self-stake-label">Boost Votes</div>
<div class="self-stake-value">+{{ (selfStakeBoostVotes / 10000).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0}) }}</div>
</div>
</div>
</div>
</div>
</div>

<!-- Votes for Block Producers Section -->
<div class="row col-12 q-mt-lg">
<div>
<p class="panel-title">Votes for Block Producers</p>
Expand All @@ -60,6 +227,24 @@ watch(() => props.account, async () => {
</template>

<style lang="scss">
.self-stake-stat {
text-align: center;
padding: 0.5rem;

.self-stake-label {
font-size: 11px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 4px;
}

.self-stake-value {
font-size: 16px;
font-weight: 600;
color: white;
}
}

.info-wrap {
flex: 1;
display: flex;
Expand Down
20 changes: 18 additions & 2 deletions src/components/validators/ValidatorDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,25 @@ export default defineComponent({
v-if="bp.self_staked_boost > 0"
color="primary"
text-color="white"
:label="`+${(bp.self_staked_boost / 10000).toLocaleString(undefined, {minimumFractionDigits: 4,maximumFractionDigits: 4,})} Boosted`"
:label="`πŸ”’ ${(bp.self_staked_amount / 10000).toLocaleString(undefined, {minimumFractionDigits: 0,maximumFractionDigits: 0,})} ${symbol} (10x Boost)`"
size="sm"
/>
>
<q-tooltip>
Self-staked: {{ (bp.self_staked_amount / 10000).toLocaleString(undefined, {minimumFractionDigits: 4,maximumFractionDigits: 4,}) }} {{ symbol }}
Β· Boost: +{{ (bp.self_staked_boost / 10000).toLocaleString(undefined, {minimumFractionDigits: 0,maximumFractionDigits: 0,}) }} votes
Β· Voting for {{ bp.num_producers_voted }}/30 BPs
</q-tooltip>
</q-chip>
<q-chip
v-else-if="bp.is_self_staking"
outline
color="positive"
text-color="positive"
:label="`πŸ”’ ${(bp.self_staked_amount / 10000).toLocaleString(undefined, {minimumFractionDigits: 0,maximumFractionDigits: 0,})} ${symbol} Self Staked`"
size="sm"
>
<q-tooltip>This BP self-stakes but hasn't activated the 10x boost yet. A re-vote or stake change will activate it.</q-tooltip>
</q-chip>
</div>
</div>
</div>
Expand Down
30 changes: 24 additions & 6 deletions src/stores/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,31 +100,49 @@ export const useChainStore = defineStore('chain', {
table: 'voters',
lower_bound: Name.from(data.owner),
limit: 1,
}) as { rows: { owner: string; self_stake_boost: number; producers: string[]; last_vote_weight: number }[] };
}) as { rows: { owner: string; self_stake_boost: number; staked: number; producers: string[]; last_vote_weight: number }[] };
let selfStakedBoost = 0;
if (producerVote.rows.length > 0 && producerVote.rows[0].owner === data.owner && producerVote.rows[0].self_stake_boost > 0 && producerVote.rows[0].producers.includes(data.owner)) {
selfStakedBoost = (producerVote.rows[0].self_stake_boost / 100) * producerVote.rows[0].last_vote_weight;
let isSelfStaking = false;
let selfStakedAmount = 0;
let numProducersVoted = 0;
if (producerVote.rows.length > 0 && producerVote.rows[0].owner === data.owner) {
isSelfStaking = producerVote.rows[0].producers.includes(data.owner);
selfStakedAmount = producerVote.rows[0].staked;
numProducersVoted = producerVote.rows[0].producers.length;
// self_stake_boost is a binary_extension<bool> on-chain; when active, the
// system contract applies a 10x multiplier to the BP's own vote weight.
// The API may return the raw value as a truthy number (e.g. 1000).
const hasBoostFlag = producerVote.rows[0].self_stake_boost > 0;
if (isSelfStaking && hasBoostFlag) {
selfStakedBoost = 10 * producerVote.rows[0].last_vote_weight;
}
}
const extraFields = {
self_staked_boost: selfStakedBoost,
is_self_staking: isSelfStaking,
self_staked_amount: selfStakedAmount,
num_producers_voted: numProducersVoted,
};
if (bp) {
try {
return {
...data,
name: bp.org.candidate_name,
location: bp.org.location.name,
self_staked_boost: selfStakedBoost,
...extraFields,
};
} catch (error) {
return {
...data,
self_staked_boost: selfStakedBoost,
...extraFields,
};
}
} else {
return {
...data,
name: data.owner,
location: '',
self_staked_boost: selfStakedBoost,
...extraFields,
};
}
}));
Expand Down
3 changes: 3 additions & 0 deletions src/types/Producers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ export interface Producer {
location: string;
name: string;
self_staked_boost: number;
is_self_staking: boolean;
self_staked_amount: number;
num_producers_voted: number;
}