using extra to have reverse option

This commit is contained in:
Pham Tu 2024-01-17 17:52:39 +07:00
parent 52e0d8f660
commit ce22688b4e
No known key found for this signature in database
GPG Key ID: 7460FD99133ADA1C
14 changed files with 578 additions and 340 deletions

View File

@ -76,5 +76,8 @@
"vite-plugin-pages": "^0.28.0",
"vue-json-viewer": "3",
"vue-tsc": "^1.0.12"
},
"resolutions": {
"cosmjs-types": "^0.9.0"
}
}

View File

@ -10,7 +10,6 @@ import type { PaginatedProposals } from '@/types';
import ProposalProcess from './ProposalProcess.vue';
import type { PropType } from 'vue';
import { computed, ref } from 'vue';
import { fromTimestamp } from 'cosmjs-types/helpers';
import type { QueryProposalsResponse } from 'cosmjs-types/cosmos/gov/v1beta1/query';
const dialog = useTxDialog();
defineProps({
@ -117,7 +116,7 @@ function metaItem(metadata: string | undefined): {
<div
class="truncate col-span-2 md:!col-span-1 text-xs text-gray-500 dark:text-gray-400 text-right md:!flex md:!justify-start"
>
{{ format.toDay(fromTimestamp(item.votingEndTime), 'from') }}
{{ format.toDay(item.votingEndTime, 'from') }}
</div>
</div>
</td>
@ -185,7 +184,7 @@ function metaItem(metadata: string | undefined): {
<div
class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end"
>
{{ format.toDay(fromTimestamp(item.votingEndTime), 'from') }}
{{ format.toDay(item.votingEndTime, 'from') }}
</div>
</div>

View File

@ -2,8 +2,6 @@
import ApexCharts from 'vue3-apexcharts';
import { computed, type PropType } from 'vue';
import { useFormatter } from '@/stores';
import { fromTimestamp } from 'cosmjs-types/helpers';
import type { CommissionRate } from '@/types';
import type { Commission } from 'cosmjs-types/cosmos/staking/v1beta1/staking';
const props = defineProps({
@ -128,12 +126,7 @@ const chartConfig = computed(() => {
<div class="bg-base-100 rounded shadow p-4">
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
{{
`Updated at ${format.toDay(
fromTimestamp(props.commission?.updateTime!),
'short'
)}`
}}
{{ `Updated at ${format.toDay(props.commission?.updateTime, 'short')}` }}
</div>
<div class="w-80 m-auto">
<ApexCharts type="donut" :options="chartConfig" :series="series" />

View File

@ -51,7 +51,9 @@ const typeMap = Object.fromEntries(
Object.entries(MsgType).map(([k, v]) => ['/' + v, k])
);
type TxType = keyof typeof MsgType;
Object.assign(typeMap, {
'/cosmos.params.v1beta1.ParameterChangeProposal': 'ParameterChangeProposal',
});
const findType = (obj: any, type: string): any => {
if (typeof obj !== 'object') return;

View File

@ -13,10 +13,13 @@ import {
setupGovExtension,
type GovProposalId,
type IbcExtension,
type AuthExtension,
type DistributionExtension,
setupIbcExtension,
setupSlashingExtension,
setupDistributionExtension,
createProtobufRpcClient,
setupAuthExtension,
} from '@cosmjs/stargate';
import {
HttpClient,
@ -40,38 +43,72 @@ import {
withCustomRequest,
} from './registry';
import { buildQuery } from '@cosmjs/tendermint-rpc/build/tendermint37/requests';
import { PageRequest, type Coin } from '@/types';
import type {
DistributionExtension,
SlashingExtension,
} from '@cosmjs/stargate/build/modules';
import { PageRequest } from '@/types';
import { PageRequest as CosmosPageRequest } from 'cosmjs-types/cosmos/base/query/v1beta1/pagination';
import type { BondStatusString } from '@cosmjs/stargate/build/modules/staking/queries';
import type { ProposalStatus } from 'cosmjs-types/cosmos/gov/v1beta1/gov';
import { fromBase64 } from '@cosmjs/encoding';
import {
QueryAccountsResponse,
QueryClientImpl,
QueryClientImpl as AuthQueryClientImpl,
} from 'cosmjs-types/cosmos/auth/v1beta1/query';
import {
QueryVotesResponse,
QueryClientImpl as GovQueryClientImpl,
QueryProposalsResponse,
} from 'cosmjs-types/cosmos/gov/v1beta1/query';
import type { Any } from 'cosmjs-types/google/protobuf/any';
import { BaseAccount } from 'cosmjs-types/cosmos/auth/v1beta1/auth';
import type { SlashingExtension } from '@cosmjs/stargate/build/modules';
import { longify } from '@cosmjs/stargate/build/queryclient';
export interface AuthExtension {
readonly auth: {
readonly account: (address: string) => Promise<BaseAccount | undefined>;
readonly accounts: () => Promise<QueryAccountsResponse>;
export interface ExtraExtension {
readonly extra: {
readonly accounts: (page?: PageRequest) => Promise<QueryAccountsResponse>;
readonly votes: (
proposalId: GovProposalId,
page?: PageRequest
) => Promise<QueryVotesResponse>;
readonly proposals: (
proposalStatus: ProposalStatus,
page?: PageRequest
) => Promise<QueryProposalsResponse>;
};
}
function setupAuthExtension(base: QueryClient) {
function setupExtraExtension(base: QueryClient) {
const rpc = createProtobufRpcClient(base);
const queryService = new QueryClientImpl(rpc);
const authQueryService = new AuthQueryClientImpl(rpc);
const govQueryService = new GovQueryClientImpl(rpc);
return {
auth: {
account: async (address: string) => {
const { account } = await queryService.Account({ address: address });
return account?.value ? BaseAccount.decode(account.value) : undefined;
extra: {
accounts: async (page?: PageRequest) => {
return await authQueryService.Accounts({
pagination: CosmosPageRequest.fromPartial({
key: page?.key ? fromBase64(page.key) : undefined,
reverse: page?.reverse ?? true,
}),
});
},
accounts: async () => {
return await queryService.Accounts();
votes: async (proposalId: GovProposalId, page?: PageRequest) => {
return await govQueryService.Votes({
proposalId: longify(proposalId),
pagination: CosmosPageRequest.fromPartial({
key: page?.key ? fromBase64(page.key) : undefined,
reverse: page?.reverse ?? true,
}),
});
},
proposals: async (proposalStatus: ProposalStatus, page?: PageRequest) => {
return await govQueryService.Proposals({
proposalStatus,
voter: '',
depositor: '',
pagination: CosmosPageRequest.fromPartial({
key: page?.key ? fromBase64(page.key) : undefined,
reverse: page?.reverse ?? true,
}),
});
},
},
};
@ -91,7 +128,8 @@ export class BaseRestClient<R extends AbstractRegistry> {
IbcExtension &
SlashingExtension &
DistributionExtension &
TxExtension;
TxExtension &
ExtraExtension;
constructor(endpoint: string, registry: R) {
this.endpoint = endpoint;
@ -116,7 +154,8 @@ export class BaseRestClient<R extends AbstractRegistry> {
setupIbcExtension,
setupSlashingExtension,
setupDistributionExtension,
setupTxExtension
setupTxExtension,
setupExtraExtension
);
}
@ -178,7 +217,7 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// if (!page) page = new PageRequest();
// const query = `?${page.toQueryString()}`;
// return this.request(this.registry.auth_accounts, {}, query);
const res = await this.queryClient.auth.accounts();
const res = await this.queryClient.extra.accounts(page);
console.log(res);
return res;
}
@ -301,8 +340,12 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}
// Gov
async getParams(subspace: string, key: string) {
console.log(this.registry.params, subspace, key);
return this.request(this.registry.params, { subspace, key });
// console.log(this.registry.params, subspace, key);
// return this.request(this.registry.params, { subspace, key });
switch (subspace) {
case 'distribution':
return await this.getDistributionParams();
}
}
async getGovParamsVoting() {
// return this.request(this.registry.gov_params_voting, {});
@ -325,11 +368,7 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
async getGovProposals(status: ProposalStatus, page?: PageRequest) {
if (!page) page = new PageRequest();
page.reverse = true;
// const query = `?proposal_status={status}&${page.toQueryString()}`;
// return this.request(this.registry.gov_proposals, { status }, query);
const paginationKey = page?.key ? fromBase64(page.key) : undefined;
const res = this.queryClient.gov.proposals(status, '', '', paginationKey);
const res = this.queryClient.extra.proposals(status, page);
console.log(res);
return res;
}
@ -361,7 +400,7 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// }
// );
}
async getGovProposalVotes(proposal_id: string, page?: PageRequest) {
async getGovProposalVotes(proposal_id: GovProposalId, page?: PageRequest) {
// if (!page) page = new PageRequest();
// page.reverse = true;
// const query = `?proposal_status={status}&${page.toQueryString()}`;
@ -371,9 +410,9 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// query
// );
const paginationKey = page?.key ? fromBase64(page.key) : undefined;
const res = await this.queryClient.gov.votes(proposal_id, paginationKey);
console.log(res);
// const paginationKey = page?.key ? fromBase64(page.key) : undefined;
const res = await this.queryClient.extra.votes(proposal_id, page);
console.log('vote', proposal_id, res);
return res;
}
async getGovProposalVotesVoter(proposal_id: string, voter: string) {

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { computed } from '@vue/reactivity';
import { fromTimestamp } from 'cosmjs-types/helpers';
import MdEditor from 'md-editor-v3';
import ObjectElement from '@/components/dynamic/ObjectElement.vue';
import {
@ -21,15 +22,25 @@ import { ref, reactive } from 'vue';
import Countdown from '@/components/Countdown.vue';
import PaginationBar from '@/components/PaginationBar.vue';
import { fromBech32, toHex } from '@cosmjs/encoding';
import type { Vote } from 'cosmjs-types/cosmos/gov/v1beta1/gov';
import {
ProposalStatus,
proposalStatusToJSON,
VoteOption,
voteOptionToJSON,
} from 'cosmjs-types/cosmos/gov/v1beta1/gov';
import type { MsgSoftwareUpgrade } from 'cosmjs-types/cosmos/upgrade/v1beta1/tx';
import type { Proposal, Vote } from 'cosmjs-types/cosmos/gov/v1beta1/gov';
import type { PageResponse } from 'cosmjs-types/cosmos/base/query/v1beta1/pagination';
import type { ParameterChangeProposal } from 'cosmjs-types/cosmos/params/v1beta1/params';
import type {
QueryDepositResponse,
QueryDepositsResponse,
} from 'cosmjs-types/cosmos/gov/v1beta1/query';
import { decodeProto } from '@/components/dynamic';
import type { Timestamp } from 'cosmjs-types/google/protobuf/timestamp';
const props = defineProps(['proposal_id', 'chain']);
const proposal = ref({} as GovProposal);
const proposal = ref({} as Proposal);
const format = useFormatter();
const store = useGovStore();
const dialog = useTxDialog();
@ -38,22 +49,29 @@ const chainStore = useBlockchain();
store.fetchProposal(props.proposal_id).then((res) => {
const proposalDetail = reactive(res.proposal);
const changeProposal = decodeProto(
proposalDetail.content!
) as ParameterChangeProposal;
Object.assign(proposalDetail.content!, changeProposal);
// when status under the voting, final_tally_result are no data, should request fetchTally
if (res.proposal?.voterStatus === 'PROPOSAL_STATUS_VOTING_PERIOD') {
if (proposalDetail.status === ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD) {
// 'PROPOSAL_STATUS_VOTING_PERIOD') {
store.fetchTally(props.proposal_id).then((tallRes) => {
proposalDetail.finalTallyResult = tallRes.tally;
});
}
proposal.value = proposalDetail;
console.log('fetchProposal', proposal.value);
// load origin params if the proposal is param change
if (proposalDetail.content?.changes) {
proposalDetail.content?.changes.forEach((item) => {
if (changeProposal?.changes) {
changeProposal.changes.forEach((item) => {
chainStore.rpc.getParams(item.subspace, item.key).then((res) => {
if (proposal.value.content && res.param) {
if (proposal.value.content && res?.params) {
if (proposal.value.content.current) {
proposal.value.content.current.push(res.param);
proposal.value.content.current.push(res.params);
} else {
proposal.value.content.current = [res.param];
proposal.value.content.current = [res.params];
}
}
});
@ -62,16 +80,22 @@ store.fetchProposal(props.proposal_id).then((res) => {
});
const color = computed(() => {
if (proposal.value.status === 'PROPOSAL_STATUS_PASSED') {
if (proposal.value.status === ProposalStatus.PROPOSAL_STATUS_PASSED) {
// 'PROPOSAL_STATUS_PASSED') {
return 'success';
} else if (proposal.value.status === 'PROPOSAL_STATUS_REJECTED') {
} else if (
proposal.value.status === ProposalStatus.PROPOSAL_STATUS_REJECTED
) {
return 'error';
}
return '';
});
const status = computed(() => {
if (proposal.value.status) {
return proposal.value.status.replace('PROPOSAL_STATUS_', '');
return proposalStatusToJSON(proposal.value.status).replace(
'PROPOSAL_STATUS_',
''
);
}
return '';
});
@ -88,7 +112,7 @@ store.fetchProposalVotes(props.proposal_id).then((x) => {
pageResponse.value = x.pagination;
});
function shortTime(v: string) {
function shortTime(v: string | Date | Timestamp) {
if (v) {
return format.toDay(v, 'from');
}
@ -97,30 +121,36 @@ function shortTime(v: string) {
const votingCountdown = computed((): number => {
const now = new Date();
const end = new Date(proposal.value.voting_end_time);
const end = proposal.value.votingEndTime
? fromTimestamp(proposal.value.votingEndTime)
: new Date();
return end.getTime() - now.getTime();
});
const upgradeCountdown = computed((): number => {
const height = Number(proposal.value.content?.plan?.height || 0);
// @ts-ignore
const upgradeSoftware = proposal.value.content as MsgSoftwareUpgrade;
const height = Number(upgradeSoftware.plan?.height || 0);
if (height > 0) {
const base = useBaseStore();
const current = Number(base.latest?.block?.header?.height || 0);
return (height - current) * 6 * 1000;
}
const now = new Date();
const end = new Date(proposal.value.content?.plan?.time || '');
const end = upgradeSoftware.plan?.time
? fromTimestamp(upgradeSoftware.plan?.time || '')
: new Date();
return end.getTime() - now.getTime();
});
const total = computed(() => {
const tally = proposal.value.final_tally_result;
const tally = proposal.value.finalTallyResult;
let sum = 0;
if (tally) {
sum += Number(tally.abstain || 0);
sum += Number(tally.yes || 0);
sum += Number(tally.no || 0);
sum += Number(tally.no_with_veto || 0);
sum += Number(tally.noWithVeto || 0);
}
return sum;
});
@ -135,7 +165,7 @@ const turnout = computed(() => {
const yes = computed(() => {
if (total.value > 0) {
const yes = proposal.value?.final_tally_result?.yes || 0;
const yes = proposal.value?.finalTallyResult?.yes || 0;
return format.percent(Number(yes) / total.value);
}
return 0;
@ -143,7 +173,7 @@ const yes = computed(() => {
const no = computed(() => {
if (total.value > 0) {
const value = proposal.value?.final_tally_result?.no || 0;
const value = proposal.value?.finalTallyResult?.no || 0;
return format.percent(Number(value) / total.value);
}
return 0;
@ -151,7 +181,7 @@ const no = computed(() => {
const veto = computed(() => {
if (total.value > 0) {
const value = proposal.value?.final_tally_result?.no_with_veto || 0;
const value = proposal.value?.finalTallyResult?.noWithVeto || 0;
return format.percent(Number(value) / total.value);
}
return 0;
@ -159,7 +189,7 @@ const veto = computed(() => {
const abstain = computed(() => {
if (total.value > 0) {
const value = proposal.value?.final_tally_result?.abstain || 0;
const value = proposal.value?.finalTallyResult?.abstain || 0;
return format.percent(Number(value) / total.value);
}
return 0;
@ -187,6 +217,7 @@ function pageload(p: number) {
pageRequest.value.setPage(p);
store.fetchProposalVotes(props.proposal_id, pageRequest.value).then((x) => {
votes.value = x.votes;
console.log('vote', votes.value);
pageResponse.value = x.pagination;
});
}
@ -297,9 +328,11 @@ function metaItem(metadata: string | undefined): {
<div class="w-2 h-2 rounded-full bg-error mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.submit_at') }}:
{{ format.toDay(proposal.submit_time) }}
{{ format.toDay(proposal.submitTime) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.submitTime) }}
</div>
<div class="text-sm">{{ shortTime(proposal.submit_time) }}</div>
</div>
<div class="flex items-center mb-4">
<div class="w-2 h-2 rounded-full bg-primary mr-3"></div>
@ -307,18 +340,20 @@ function metaItem(metadata: string | undefined): {
{{ $t('gov.deposited_at') }}:
{{
format.toDay(
proposal.status === 'PROPOSAL_STATUS_DEPOSIT_PERIOD'
? proposal.deposit_end_time
: proposal.voting_start_time
proposal.status ===
ProposalStatus.PROPOSAL_STATUS_DEPOSIT_PERIOD
? proposal.depositEndTime
: proposal.votingStartTime
)
}}
</div>
<div class="text-sm">
{{
shortTime(
proposal.status === 'PROPOSAL_STATUS_DEPOSIT_PERIOD'
? proposal.deposit_end_time
: proposal.voting_start_time
proposal.status ===
ProposalStatus.PROPOSAL_STATUS_DEPOSIT_PERIOD
? proposal.depositEndTime
: proposal.votingStartTime
)
}}
</div>
@ -328,10 +363,10 @@ function metaItem(metadata: string | undefined): {
<div class="w-2 h-2 rounded-full bg-yes mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.vote_start_from') }}
{{ format.toDay(proposal.voting_start_time) }}
{{ format.toDay(proposal.votingStartTime) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_start_time) }}
{{ shortTime(proposal.votingStartTime) }}
</div>
</div>
<div class="pl-5 text-sm mt-2">
@ -343,10 +378,10 @@ function metaItem(metadata: string | undefined): {
<div class="w-2 h-2 rounded-full bg-success mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.vote_end') }}
{{ format.toDay(proposal.voting_end_time) }}
{{ format.toDay(proposal.votingEndTime) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_end_time) }}
{{ shortTime(proposal.votingEndTime) }}
</div>
</div>
<div class="pl-5 text-sm">
@ -358,7 +393,7 @@ function metaItem(metadata: string | undefined): {
<div
class="mt-4"
v-if="
proposal?.content?.['@type']?.endsWith('SoftwareUpgradeProposal')
proposal?.content?.typeUrl.endsWith('SoftwareUpgradeProposal')
"
>
<div class="flex items-center">
@ -373,7 +408,7 @@ function metaItem(metadata: string | undefined): {
}}</span>
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_end_time) }}
{{ shortTime(proposal.votingEndTime) }}
</div>
</div>
<div class="pl-5 text-sm mt-2">
@ -395,8 +430,9 @@ function metaItem(metadata: string | undefined): {
v-if="item.option"
class="py-2 text-sm"
:class="{
'text-yes': item.option === 'VOTE_OPTION_YES',
'text-gray-400': item.option === 'VOTE_OPTION_ABSTAIN',
'text-yes': item.option === VoteOption.VOTE_OPTION_YES,
'text-gray-400':
item.option === VoteOption.VOTE_OPTION_ABSTAIN,
}"
>
{{ String(item.option).replace('VOTE_OPTION_', '') }}
@ -406,7 +442,7 @@ function metaItem(metadata: string | undefined): {
item.options
.map(
(x) =>
`${x.option.replace(
`${voteOptionToJSON(x.option).replace(
'VOTE_OPTION_',
''
)}:${format.percent(x.weight)}`
@ -419,7 +455,7 @@ function metaItem(metadata: string | undefined): {
</table>
<PaginationBar
:limit="pageRequest.limit"
:total="pageResponse?.total.toString()"
:total="pageResponse?.total?.toString()"
:callback="pageload"
/>
</div>

View File

@ -9,53 +9,81 @@ import ChainRegistryClient from '@ping-pub/chain-registry-client';
import type { IBCPath } from '@ping-pub/chain-registry-client/dist/types';
import router from '@/router';
import { useIBCModule } from './connStore';
import type { PageResponse } from 'cosmjs-types/cosmos/base/query/v1beta1/pagination';
import type { IdentifiedConnection } from 'cosmjs-types/ibc/core/connection/v1/connection';
const props = defineProps(['chain']);
const chainStore = useBlockchain();
const ibcStore = useIBCModule()
const list = ref([] as Connection[]);
const pageRequest = ref(new PageRequest())
const pageResponse = ref({} as Pagination)
const ibcStore = useIBCModule();
const list = ref([] as IdentifiedConnection[]);
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as PageResponse | undefined);
const tab = ref('registry');
onMounted(() => {
pageload(1)
ibcStore.load()
pageload(1);
ibcStore.load();
});
function pageload(p: number) {
pageRequest.value.setPage(p)
pageRequest.value.setPage(p);
chainStore.rpc.getIBCConnections(pageRequest.value).then((x) => {
list.value = x.connections;
pageResponse.value = x.pagination
if(x.pagination.total && Number(x.pagination.total) > 0) {
ibcStore.showConnection(0)
pageResponse.value = x.pagination;
if (x.pagination?.total && Number(x.pagination.total) > 0) {
ibcStore.showConnection(0);
}
});
}
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="flex flex-wrap gap-4 items-center">
<div class="flex flex-wrap gap-4 items-center">
<h2 class="card-title py-4">{{ $t('ibc.title') }}</h2>
<div class="tabs tabs-boxed">
<a class="tab" :class="{ 'tab-active': tab === 'registry' }" @click="tab = 'registry'">{{ $t('ibc.registry') }}</a>
<a class="tab" :class="{ 'tab-active': tab === 'favorite' }" @click="tab = 'favorite'">{{ $t('module.favorite') }}</a>
<a
class="tab"
:class="{ 'tab-active': tab === 'registry' }"
@click="tab = 'registry'"
>{{ $t('ibc.registry') }}</a
>
<a
class="tab"
:class="{ 'tab-active': tab === 'favorite' }"
@click="tab = 'favorite'"
>{{ $t('module.favorite') }}</a
>
</div>
</div>
<div>
<div v-show="tab === 'registry'" class="flex flex-wrap gap-1 p-4 ">
<span v-for="s in ibcStore.commonIBCs" class="btn btn-xs btn-link mr-1" @click="ibcStore.fetchConnection(s.path)">{{ s.from }}
&#x21cc; {{ s.to }}</span>
<div v-show="tab === 'registry'" class="flex flex-wrap gap-1 p-4">
<span
v-for="s in ibcStore.commonIBCs"
class="btn btn-xs btn-link mr-1"
@click="ibcStore.fetchConnection(s.path)"
>{{ s.from }} &#x21cc; {{ s.to }}</span
>
</div>
<div v-show="tab === 'favorite'" class="flex flex-wrap gap-1 p-4 ">
<div v-show="tab === 'favorite'" class="flex flex-wrap gap-1 p-4">
<div class="join border border-primary">
<button class="join-item px-2">{{ $t('ibc.connection_id') }}:</button>
<input v-model="ibcStore.connectionId" type=number class="input input-bordered w-40 join-item" min="0"
:max="pageResponse.total || 0" :placeholder="`0~${pageResponse.total}`" />
<button class="join-item btn btn-primary" @click="ibcStore.showConnection()">{{ $t('ibc.btn_apply') }}</button>
<button class="join-item px-2">
{{ $t('ibc.connection_id') }}:
</button>
<input
v-model="ibcStore.connectionId"
type="number"
class="input input-bordered w-40 join-item"
min="0"
:max="pageResponse?.total.toString()"
:placeholder="`0~${pageResponse?.total.toString()}`"
/>
<button
class="join-item btn btn-primary"
@click="ibcStore.showConnection()"
>
{{ $t('ibc.btn_apply') }}
</button>
</div>
</div>
</div>

View File

@ -57,7 +57,7 @@ onMounted(() => {
clientState.value = x.identifiedClientState;
if (x.identifiedClientState?.clientState) {
Object.assign(
clientState.value?.clientState!,
clientState.value!,
TendermintClientState.decode(
x.identifiedClientState?.clientState.value
)
@ -174,7 +174,7 @@ function color(v: string) {
<div
class="order-first text-3xl font-semibold tracking-tight text-main mb-2"
>
{{ clientState?.clientState?.chainId }}
{{ clientState?.chainId }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
{{ conn?.counterparty?.connectionId }} {{ clientState?.clientId }}
@ -202,8 +202,8 @@ function color(v: string) {
<tr>
<td class="w-52">{{ $t('ibc.trust_level') }}:</td>
<td>
{{ clientState?.clientState?.trustLevel.numerator }}/{{
clientState.clientState?.trustLevel.denominator
{{ clientState?.trustLevel.numerator }}/{{
clientState?.trustLevel.denominator
}}
</td>
</tr>
@ -211,9 +211,7 @@ function color(v: string) {
<td class="w-52">{{ $t('ibc.trusting_period') }}:</td>
<td>
{{
formatSeconds(
clientState.clientState?.trustingPeriod.seconds.toString()
)
formatSeconds(clientState?.trustingPeriod.seconds.toString())
}}
</td>
</tr>
@ -221,9 +219,7 @@ function color(v: string) {
<td class="w-52">{{ $t('ibc.unbonding_period') }}:</td>
<td>
{{
formatSeconds(
clientState.clientState?.unbondingPeriod.seconds.toString()
)
formatSeconds(clientState?.unbondingPeriod.seconds.toString())
}}
</td>
</tr>
@ -231,26 +227,20 @@ function color(v: string) {
<td class="w-52">{{ $t('ibc.max_clock_drift') }}:</td>
<td>
{{
formatSeconds(
clientState.clientState?.maxClockDrift.seconds.toString()
)
formatSeconds(clientState?.maxClockDrift.seconds.toString())
}}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.frozen_height') }}:</td>
<td>
{{
clientState.clientState?.frozenHeight.revisionHeight.toString()
}}
{{ clientState?.frozenHeight.revisionHeight.toString() }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.latest_height') }}:</td>
<td>
{{
clientState.clientState?.latestHeight.revisionHeight.toString()
}}
{{ clientState?.latestHeight.revisionHeight.toString() }}
</td>
</tr>
</tbody>
@ -266,9 +256,7 @@ function color(v: string) {
<td colspan="2">
<div class="flex justify-between">
<span>{{ $t('ibc.allow_update_after_expiry') }}:</span>
<span>{{
clientState.clientState?.allowUpdateAfterExpiry
}}</span>
<span>{{ clientState?.allowUpdateAfterExpiry }}</span>
</div>
</td>
</tr>
@ -276,16 +264,14 @@ function color(v: string) {
<td colspan="2">
<div class="flex justify-between">
<span>{{ $t('ibc.allow_update_after_misbehaviour') }}: </span>
<span>{{
clientState.clientState?.allowUpdateAfterMisbehaviour
}}</span>
<span>{{ clientState?.allowUpdateAfterMisbehaviour }}</span>
</div>
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.upgrade_path') }}:</td>
<td class="text-right">
{{ clientState.clientState?.upgradePath.join(', ') }}
{{ clientState?.upgradePath.join(', ') }}
</td>
</tr>
</tbody>
@ -389,11 +375,11 @@ function color(v: string) {
<td>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit"
:class="`text-${color(v.state)}`"
:class="`text-${color(v.state.toString())}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${color(v.state)}`"
:class="`bg-${color(v.state.toString())}`"
></span>
{{ v.state }}
</div>

View File

@ -17,7 +17,7 @@ import { computed } from '@vue/reactivity';
import CardStatisticsVertical from '@/components/CardStatisticsVertical.vue';
import ProposalListItem from '@/components/ProposalListItem.vue';
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue'
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue';
const props = defineProps(['chain']);
@ -27,7 +27,7 @@ const walletStore = useWalletStore();
const format = useFormatter();
const dialog = useTxDialog();
const stakingStore = useStakingStore();
const paramStore = useParamStore()
const paramStore = useParamStore();
const coinInfo = computed(() => {
return store.coinInfo;
});
@ -35,19 +35,19 @@ const coinInfo = computed(() => {
onMounted(() => {
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
paramStore.handleAbciInfo();
// if(!(coinInfo.value && coinInfo.value.name)) {
// }
});
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]);
const currName = ref("")
const currName = ref('');
blockchain.$subscribe((m, s) => {
if (s.chainName !== currName.value) {
currName.value = s.chainName
currName.value = s.chainName;
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
paramStore.handleAbciInfo();
}
});
function shortName(name: string, id: string) {
@ -97,53 +97,62 @@ const color = computed(() => {
});
function updateState() {
walletStore.loadMyAsset()
walletStore.loadMyAsset();
}
function trustColor(v: string) {
return `text-${colorMap(v)}`
return `text-${colorMap(v)}`;
}
const quantity = ref(100)
const quantity = ref(100);
const qty = computed({
get: () => {
return parseFloat(quantity.value.toFixed(6))
return parseFloat(quantity.value.toFixed(6));
},
set: val => {
quantity.value = val
}
})
set: (val) => {
quantity.value = val;
},
});
const amount = computed({
get: () => {
return quantity.value * ticker.value.converted_last.usd || 0
return quantity.value * ticker.value.converted_last.usd || 0;
},
set: val => {
quantity.value = val / ticker.value.converted_last.usd || 0
}
})
set: (val) => {
quantity.value = val / ticker.value.converted_last.usd || 0;
},
});
</script>
<template>
<div>
<div v-if="coinInfo && coinInfo.name" class="bg-base-100 rounded shadow mb-4">
<div
v-if="coinInfo && coinInfo.name"
class="bg-base-100 rounded shadow mb-4"
>
<div class="grid grid-cols-2 md:grid-cols-3 p-4">
<div class="col-span-2 md:col-span-1">
<div class="text-xl font-semibold text-main">
{{ coinInfo.name }} (<span class="uppercase">{{
coinInfo.symbol
}}</span>)
}}</span
>)
</div>
<div class="text-xs mt-2">
{{ $t('index.rank') }}:
<div class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400">
<div
class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400"
>
#{{ coinInfo.market_cap_rank }}
</div>
</div>
<div class="my-4 flex flex-wrap items-center">
<a v-for="(item, index) of comLinks" :key="index" :href="item.href"
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center">
<a
v-for="(item, index) of comLinks"
:key="index"
:href="item.href"
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center"
>
<Icon :icon="item?.icon" />
<span class="ml-1 text-sm uppercase">{{ item?.name }}</span>
</a>
@ -153,9 +162,12 @@ const amount = computed({
<div class="dropdown dropdown-hover w-full">
<label>
<div
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded">
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded"
>
<div>
<div class="font-semibold text-xl text-[#666] dark:text-white">
<div
class="font-semibold text-xl text-[#666] dark:text-white"
>
{{ ticker?.market?.name || '' }}
</div>
<div class="text-info text-sm">
@ -166,7 +178,9 @@ const amount = computed({
</div>
<div class="text-right">
<div class="text-xl font-semibold text-[#666] dark:text-white">
<div
class="text-xl font-semibold text-[#666] dark:text-white"
>
${{ ticker?.converted_last?.usd }}
</div>
<div class="text-sm" :class="store.priceColor">
@ -178,10 +192,19 @@ const amount = computed({
<div class="dropdown-content pt-1">
<div class="h-64 overflow-auto w-full shadow rounded">
<ul class="menu w-full bg-gray-100 rounded dark:bg-[#384059]">
<li v-for="(item, index) in store.coinInfo.tickers" :key="index" @click="store.selectTicker(index)">
<div class="flex items-center justify-between hover:bg-base-100">
<li
v-for="(item, index) in store.coinInfo.tickers"
:key="index"
@click="store.selectTicker(index)"
>
<div
class="flex items-center justify-between hover:bg-base-100"
>
<div class="flex-1">
<div class="text-main text-sm" :class="trustColor(item.trust_score)">
<div
class="text-main text-sm"
:class="trustColor(item.trust_score)"
>
{{ item?.market?.name }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
@ -192,7 +215,7 @@ const amount = computed({
</div>
<div class="text-base text-main">
${{ item?.converted_last?.usd }}
${{ item?.converted_last?.usd }}
</div>
</div>
</li>
@ -203,37 +226,92 @@ const amount = computed({
<div class="flex">
<label class="btn btn-primary !px-1 my-5 mr-2" for="calculator">
<svg class="w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <rect x="4" y="2" width="16" height="20" rx="2"></rect> <line x1="8" x2="16" y1="6" y2="6"></line> <line x1="16" x2="16" y1="14" y2="18"></line> <path d="M16 10h.01"></path> <path d="M12 10h.01"></path> <path d="M8 10h.01"></path> <path d="M12 14h.01"></path> <path d="M8 14h.01"></path> <path d="M12 18h.01"></path> <path d="M8 18h.01"></path> </g></svg>
<svg
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<rect x="4" y="2" width="16" height="20" rx="2"></rect>
<line x1="8" x2="16" y1="6" y2="6"></line>
<line x1="16" x2="16" y1="14" y2="18"></line>
<path d="M16 10h.01"></path>
<path d="M12 10h.01"></path>
<path d="M8 10h.01"></path>
<path d="M12 14h.01"></path>
<path d="M8 14h.01"></path>
<path d="M12 18h.01"></path>
<path d="M8 18h.01"></path>
</g>
</svg>
</label>
<!-- Put this part before </body> tag -->
<input type="checkbox" id="calculator" class="modal-toggle" />
<div class="modal">
<div class="modal-box">
<h3 class="text-lg font-bold">{{ $t('index.price_calculator') }}</h3>
<h3 class="text-lg font-bold">
{{ $t('index.price_calculator') }}
</h3>
<div class="flex flex-col w-full mt-5">
<div class="grid h-20 flex-grow card rounded-box place-items-center">
<div
class="grid h-20 flex-grow card rounded-box place-items-center"
>
<div class="join w-full">
<label class="join-item btn">
<span class="uppercase">{{ coinInfo.symbol }}</span>
</label>
<input type="number" v-model="qty" min="0" placeholder="Input a number" class="input grow input-bordered join-item" />
<input
type="number"
v-model="qty"
min="0"
placeholder="Input a number"
class="input grow input-bordered join-item"
/>
</div>
</div>
<div class="divider">=</div>
<div class="grid h-20 flex-grow card rounded-box place-items-center">
<div
class="grid h-20 flex-grow card rounded-box place-items-center"
>
<div class="join w-full">
<label class="join-item btn">
<span>USD</span>
</label>
<input type="number" v-model="amount" min="0" placeholder="Input amount" class="join-item grow input input-bordered" />
<input
type="number"
v-model="amount"
min="0"
placeholder="Input amount"
class="join-item grow input input-bordered"
/>
</div>
</div>
</div>
</div>
<label class="modal-backdrop" for="calculator">{{ $t('index.close') }}</label>
<label class="modal-backdrop" for="calculator">{{
$t('index.close')
}}</label>
</div>
<a class="my-5 !text-white btn grow" :class="{'!btn-success': store.trustColor === 'green', '!btn-warning': store.trustColor === 'yellow'}" :href="ticker.trade_url"
target="_blank">
<a
class="my-5 !text-white btn grow"
:class="{
'!btn-success': store.trustColor === 'green',
'!btn-warning': store.trustColor === 'yellow',
}"
:href="ticker.trade_url"
target="_blank"
>
{{ $t('index.buy') }} {{ coinInfo.symbol || '' }}
</a>
</div>
@ -246,11 +324,16 @@ const amount = computed({
</div>
<div class="h-[1px] w-full bg-gray-100 dark:bg-[#384059]"></div>
<div class="max-h-[250px] overflow-auto p-4 text-sm">
<MdEditor :model-value="coinInfo.description?.en" previewOnly></MdEditor>
<MdEditor
:model-value="coinInfo.description?.en"
previewOnly
></MdEditor>
</div>
<div class="mx-4 flex flex-wrap items-center">
<div v-for="tag in coinInfo.categories"
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1">
<div
v-for="tag in coinInfo.categories"
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1"
>
{{ tag }}
</div>
</div>
@ -262,26 +345,41 @@ const amount = computed({
</div>
</div>
<div v-if="blockchain.supportModule('governance')" class="bg-base-100 rounded mt-4 shadow">
<div
v-if="blockchain.supportModule('governance')"
class="bg-base-100 rounded mt-4 shadow"
>
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.active_proposals') }}
</div>
<div class="px-4 pb-4">
<ProposalListItem :proposals="store?.proposals" />
</div>
<div class="pb-8 text-center" v-if="store.proposals?.proposals?.length === 0">
<div
class="pb-8 text-center"
v-if="store.proposals?.proposals?.length === 0"
>
{{ $t('index.no_active_proposals') }}
</div>
</div>
<div class="bg-base-100 rounded mt-4 shadow">
<div class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main">
<span class="truncate" >{{ walletStore.currentAddress || 'Not Connected' }}</span>
<RouterLink v-if="walletStore.currentAddress"
<div
class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main"
>
<span class="truncate">{{
walletStore.currentAddress || 'Not Connected'
}}</span>
<RouterLink
v-if="walletStore.currentAddress"
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
:to="`/${chain}/account/${walletStore.currentAddress}`">{{ $t('index.more') }}</RouterLink>
:to="`/${chain}/account/${walletStore.currentAddress}`"
>{{ $t('index.more') }}</RouterLink
>
</div>
<div class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6">
<div
class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6"
>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('account.balance') }}</div>
<div class="text-lg font-semibold text-main">
@ -320,7 +418,10 @@ const amount = computed({
</div>
</div>
<div v-if="walletStore.delegations.length > 0" class="px-4 pb-4 overflow-auto">
<div
v-if="walletStore.delegations.length > 0"
class="px-4 pb-4 overflow-auto"
>
<table class="table table-compact w-full table-zebra">
<thead>
<tr>
@ -333,12 +434,15 @@ const amount = computed({
<tbody>
<tr v-for="(item, index) in walletStore.delegations" :key="index">
<td>
<RouterLink class="link link-primary no-underline" :to="`/${chain}/staking/${item?.delegation?.validator_address}`">
{{
format.validatorFromBech32(
item?.delegation?.validator_address
)
}}
<RouterLink
class="link link-primary no-underline"
:to="`/${chain}/staking/${item?.delegation?.validatorAddress}`"
>
{{
format.validatorFromBech32(
item?.delegation?.validatorAddress
)
}}
</RouterLink>
</td>
<td>{{ format.formatToken(item?.balance) }}</td>
@ -347,19 +451,38 @@ const amount = computed({
format.formatTokens(
walletStore?.rewards?.rewards?.find(
(el) =>
el?.validator_address ===
item?.delegation?.validator_address
)?.reward)
el?.validatorAddress ===
item?.delegation?.validatorAddress
)?.reward
)
}}
</td>
<td>
<div>
<label for="delegate" class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
@click="dialog.open('delegate', { validator_address: item.delegation.validator_address }, updateState)">
<label
for="delegate"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
@click="
dialog.open(
'delegate',
{ validator_address: item.delegation.validatorAddress },
updateState
)
"
>
{{ $t('account.btn_delegate') }}
</label>
<label for="withdraw" class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
@click="dialog.open('withdraw', { validator_address: item.delegation.validator_address }, updateState)">
<label
for="withdraw"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
@click="
dialog.open(
'withdraw',
{ validator_address: item.delegation.validatorAddress },
updateState
)
"
>
{{ $t('index.btn_withdraw_reward') }}
</label>
</div>
@ -370,15 +493,33 @@ const amount = computed({
</div>
<div class="grid grid-cols-3 gap-4 px-4 pb-6 mt-4">
<label for="PingTokenConvert" class="btn btn-primary text-white">{{ $t('index.btn_swap') }}</label>
<label for="send" class="btn !bg-yes !border-yes text-white" @click="dialog.open('send', {}, updateState)">{{ $t('account.btn_send') }}</label>
<label for="delegate" class="btn !bg-info !border-info text-white"
@click="dialog.open('delegate', {}, updateState)">{{ $t('account.btn_delegate') }}</label>
<RouterLink to="/wallet/receive" class="btn !bg-info !border-info text-white hidden">{{ $t('index.receive') }}</RouterLink>
<label for="PingTokenConvert" class="btn btn-primary text-white">{{
$t('index.btn_swap')
}}</label>
<label
for="send"
class="btn !bg-yes !border-yes text-white"
@click="dialog.open('send', {}, updateState)"
>{{ $t('account.btn_send') }}</label
>
<label
for="delegate"
class="btn !bg-info !border-info text-white"
@click="dialog.open('delegate', {}, updateState)"
>{{ $t('account.btn_delegate') }}</label
>
<RouterLink
to="/wallet/receive"
class="btn !bg-info !border-info text-white hidden"
>{{ $t('index.receive') }}</RouterLink
>
</div>
<Teleport to="body">
<ping-token-convert :chain-name="blockchain?.current?.prettyName" :endpoint="blockchain?.endpoint?.address"
:hd-path="walletStore?.connectedWallet?.hdPath"></ping-token-convert>
<ping-token-convert
:chain-name="blockchain?.current?.prettyName"
:endpoint="blockchain?.endpoint?.address"
:hd-path="walletStore?.connectedWallet?.hdPath"
></ping-token-convert>
</Teleport>
</div>
@ -387,7 +528,10 @@ const amount = computed({
{{ $t('index.app_versions') }}
</div>
<!-- Application Version -->
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
<ArrayObjectElement
:value="paramStore.appVersion?.items"
:thead="false"
/>
<div class="h-4"></div>
</div>
@ -395,7 +539,10 @@ const amount = computed({
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.node_info') }}
</div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
<ArrayObjectElement
:value="paramStore.nodeVersion?.items"
:thead="false"
/>
<div class="h-4"></div>
</div>
</div>

View File

@ -519,7 +519,7 @@ function mapDelegators(messages: any[]) {
.startsWith('1970')
"
>
{{ format.toDay(fromTimestamp(v.unbondingTime), 'from') }}
{{ format.toDay(v.unbondingTime, 'from') }}
</h4>
<h4 v-else>-</h4>
<span class="text-sm">{{ $t('staking.unbonding_time') }}</span>

View File

@ -11,9 +11,13 @@ import { useStakingStore } from './useStakingStore';
import { fromBase64, fromBech32, fromHex, toHex } from '@cosmjs/encoding';
import { consensusPubkeyToHexAddress, get } from '@/libs';
import { useBankStore } from './useBankStore';
import type { Coin, DenomTrace } from '@/types';
// import type { Coin, DenomTrace } from '@/types';
import { useDashboard } from './useDashboard';
import type { Asset } from '@ping-pub/chain-registry-client/dist/types'
import type { Asset } from '@ping-pub/chain-registry-client/dist/types';
import type { DenomTrace } from 'cosmjs-types/ibc/applications/transfer/v1/transfer';
import type { Coin } from '@cosmjs/stargate';
import type { Timestamp } from 'cosmjs-types/google/protobuf/timestamp';
import { fromTimestamp } from 'cosmjs-types/helpers';
dayjs.extend(localeData);
dayjs.extend(duration);
@ -66,39 +70,41 @@ export const useFormatter = defineStore('formatter', {
let trace = this.ibcDenoms[hash];
if (!trace) {
trace = (await this.blockchain.rpc.getIBCAppTransferDenom(hash))
.denom_trace;
.denomTrace!;
this.ibcDenoms[hash] = trace;
}
return trace;
},
async fetchDenomMetadata(denom: string) {
if(this.loading.includes(denom)) return
this.loading.push(denom)
const asset = await get(`https://metadata.ping.pub/metadata/${denom}`) as Asset
this.ibcMetadata[denom] = asset
if (this.loading.includes(denom)) return;
this.loading.push(denom);
const asset = (await get(
`https://metadata.ping.pub/metadata/${denom}`
)) as Asset;
this.ibcMetadata[denom] = asset;
},
priceInfo(denom: string) {
const id = this.dashboard.coingecko[denom]?.coinId || "";
const id = this.dashboard.coingecko[denom]?.coinId || '';
const prices = this.dashboard.prices[id];
return prices;
},
color(change?: number) {
if(!change) return ""
if (!change) return '';
switch (true) {
case change > 0:
return "text-success"
return 'text-success';
case change < 0:
return "text-error"
return 'text-error';
default:
return ""
return '';
}
},
priceColor(denom: string, currency = "usd") {
const change = this.priceChanges(denom, currency)
return this.color(change)
priceColor(denom: string, currency = 'usd') {
const change = this.priceChanges(denom, currency);
return this.color(change);
},
price(denom: string, currency = "usd") {
if(!denom || denom.length < 2) return 0
price(denom: string, currency = 'usd') {
if (!denom || denom.length < 2) return 0;
const info = this.priceInfo(denom);
return info ? info[currency] || 0 : 0;
},
@ -107,33 +113,38 @@ export const useFormatter = defineStore('formatter', {
return info ? info[`${currency}_24h_change`] || 0 : 0;
},
showChanges(v?: number) {
return v!==0 ? numeral(v).format("+0,0.[00]"): ""
return v !== 0 ? numeral(v).format('+0,0.[00]') : '';
},
tokenValue(token?: Coin) {
if(token) {
return numeral(this.tokenValueNumber(token)).format("0,0.[00]")
if (token) {
return numeral(this.tokenValueNumber(token)).format('0,0.[00]');
}
return ""
return '';
},
specialDenom(denom: string) {
switch(true) {
case denom.startsWith('u'): return 6
case denom.startsWith("a"): return 18
case denom==='inj': return 18
switch (true) {
case denom.startsWith('u'):
return 6;
case denom.startsWith('a'):
return 18;
case denom === 'inj':
return 18;
}
return 0
return 0;
},
tokenValueNumber(token?: Coin) {
if(!token || !token.denom) return 0
// find the symbol,
const symbol = this.dashboard.coingecko[token.denom]?.symbol || token.denom
if (!token || !token.denom) return 0;
// find the symbol,
const symbol =
this.dashboard.coingecko[token.denom]?.symbol || token.denom;
// convert denomation to to symbol
const exponent =
this.dashboard.coingecko[symbol?.toLowerCase()]?.exponent || this.specialDenom(token.denom);
this.dashboard.coingecko[symbol?.toLowerCase()]?.exponent ||
this.specialDenom(token.denom);
// cacualte amount of symbol
const amount = Number(token.amount) / (10 ** exponent)
const value = amount * this.price(token.denom)
return value
const amount = Number(token.amount) / 10 ** exponent;
const value = amount * this.price(token.denom);
return value;
},
formatTokenAmount(token: { denom: string; amount: string }) {
return this.formatToken(token, false);
@ -141,34 +152,33 @@ export const useFormatter = defineStore('formatter', {
formatToken2(token: { denom: string; amount: string }, withDenom = true) {
return this.formatToken(token, true, '0,0.[00]');
},
findGlobalAssetConfig(denom: string) {
const chains = Object.values(this.dashboard.chains)
for ( let i =0; i < chains.length; i++ ) {
const assets = chains[i].assets
const conf = assets.find(a => a.base === denom)
if(conf) {
return conf
const chains = Object.values(this.dashboard.chains);
for (let i = 0; i < chains.length; i++) {
const assets = chains[i].assets;
const conf = assets.find((a) => a.base === denom);
if (conf) {
return conf;
}
}
return undefined
return undefined;
},
tokenDisplayDenom(denom?: string) {
if (denom) {
let asset: Asset | undefined;
if (denom && denom.startsWith('ibc/')) {
const ibcDenom = denom.replace('ibc/', '')
asset = this.ibcMetadata[ibcDenom];
if(!asset) {
const ibcDenom = denom.replace('ibc/', '');
asset = this.ibcMetadata[ibcDenom];
if (!asset) {
// update ibc metadata if not exits in local cache
this.fetchDenomMetadata(ibcDenom)
this.fetchDenomMetadata(ibcDenom);
} else {
console.log("ibc metadata", asset)
console.log('ibc metadata', asset);
}
} else {
asset = this.findGlobalAssetConfig(denom)
asset = this.findGlobalAssetConfig(denom);
}
if (asset) {
@ -178,7 +188,7 @@ export const useFormatter = defineStore('formatter', {
if (x.exponent >= unit.exponent) {
unit = x;
}
});
});
return unit.denom;
}
return denom;
@ -192,15 +202,18 @@ export const useFormatter = defineStore('formatter', {
let amount = Number(token.amount);
let denom = token.denom;
let conf = mode === 'local'? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
): this.findGlobalAssetConfig(token.denom)
let conf =
mode === 'local'
? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
)
: this.findGlobalAssetConfig(token.denom);
if (denom && denom.startsWith('ibc/')) {
conf = this.ibcMetadata[denom.replace('ibc/', '')];
if (!conf) {
this.fetchDenomMetadata(denom.replace('ibc/', ''))
this.fetchDenomMetadata(denom.replace('ibc/', ''));
}
}
@ -230,15 +243,18 @@ export const useFormatter = defineStore('formatter', {
let amount = Number(token.amount);
let denom = token.denom;
let conf = mode === 'local'? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
): this.findGlobalAssetConfig(token.denom)
let conf =
mode === 'local'
? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
)
: this.findGlobalAssetConfig(token.denom);
if (denom && denom.startsWith('ibc/')) {
conf = this.ibcMetadata[denom.replace('ibc/', '')];
if (!conf) {
this.fetchDenomMetadata(denom.replace('ibc/', ''))
this.fetchDenomMetadata(denom.replace('ibc/', ''));
}
}
@ -255,11 +271,11 @@ export const useFormatter = defineStore('formatter', {
denom = unit.denom.toUpperCase();
}
}
if(amount < 0.000001) {
if (amount < 0.000001) {
return `0 ${denom.substring(0, 10)}`;
}
if(amount < 0.01) {
fmt = '0.[000000]'
if (amount < 0.01) {
fmt = '0.[000000]';
}
return `${numeral(amount).format(fmt)} ${
withDenom ? denom.substring(0, 10) : ''
@ -291,7 +307,7 @@ export const useFormatter = defineStore('formatter', {
const txt = toHex(fromBase64(address)).toUpperCase();
const validator = this.staking.validators.find(
(x) => consensusPubkeyToHexAddress(x.consensus_pubkey) === txt
(x) => consensusPubkeyToHexAddress(x.consensusPubkey) === txt
);
return validator?.description?.moniker;
},
@ -299,7 +315,7 @@ export const useFormatter = defineStore('formatter', {
validatorFromBech32(address: string) {
if (!address) return address;
const validator = this.staking.validators.find(
(x) => x.operator_address === address
(x) => x.operatorAddress === address
);
return validator?.description?.moniker;
},
@ -319,34 +335,39 @@ export const useFormatter = defineStore('formatter', {
return decimal ? numeral(decimal).format('0.[00]%') : '-';
},
formatNumber(input?: number, fmt = '0.[00]') {
if(!input) return ""
return numeral(input).format(fmt)
if (!input) return '';
return numeral(input).format(fmt);
},
numberAndSign(input: number, fmt = '+0,0') {
return numeral(input).format(fmt);
},
toLocaleDate(time?: string | number | Date) {
if(!time) return ""
return new Date(time).toLocaleString(navigator.language)
},
toDay(time?: string | number| Date, format = 'long') {
if (!time) return '';
return new Date(time).toLocaleString(navigator.language);
},
toDay(time?: string | number | Date | Timestamp, format = 'long') {
if (!time) return '';
const timeValue =
typeof time === 'object' && 'seconds' in time
? fromTimestamp(time)
: time;
if (format === 'long') {
return dayjs(time).format('YYYY-MM-DD HH:mm');
return dayjs(timeValue).format('YYYY-MM-DD HH:mm');
}
if (format === 'date') {
return dayjs(time).format('YYYY-MM-DD');
return dayjs(timeValue).format('YYYY-MM-DD');
}
if (format === 'time') {
return dayjs(time).format('HH:mm:ss');
return dayjs(timeValue).format('HH:mm:ss');
}
if (format === 'from') {
return dayjs(time).fromNow();
return dayjs(timeValue).fromNow();
}
if (format === 'to') {
return dayjs(time).toNow();
return dayjs(timeValue).toNow();
}
return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
return dayjs(timeValue).format('YYYY-MM-DD HH:mm:ss');
},
messages(msgs: { '@type'?: string; typeUrl?: string }[]) {
if (msgs) {

View File

@ -11,6 +11,7 @@ import {
TextProposal,
} from 'cosmjs-types/cosmos/gov/v1beta1/gov';
import type { QueryProposalsResponse } from 'cosmjs-types/cosmos/gov/v1beta1/query';
import type { GovProposalId } from '@cosmjs/stargate';
export const useGovStore = defineStore('govStore', {
state: () => {
@ -105,7 +106,7 @@ export const useGovStore = defineStore('govStore', {
async fetchProposalDeposits(proposalId: string) {
return await this.blockchain.rpc.getGovProposalDeposits(proposalId);
},
async fetchProposalVotes(proposalId: string, page?: PageRequest) {
async fetchProposalVotes(proposalId: GovProposalId, page?: PageRequest) {
return await this.blockchain.rpc.getGovProposalVotes(proposalId, page);
},
async fetchProposalVotesVoter(proposalId: string, voter: string) {

View File

@ -3,22 +3,30 @@ import { useBlockchain } from './useBlockchain';
import { fromBech32, toBech32 } from '@cosmjs/encoding';
import type {
Delegation,
Coin,
UnbondingResponses,
DelegatorRewards,
WalletConnected,
} from '@/types';
import { useStakingStore } from './useStakingStore';
import router from '@/router'
import router from '@/router';
import type {
DelegationResponse,
UnbondingDelegation,
} from 'cosmjs-types/cosmos/staking/v1beta1/staking';
import type { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
import type {
QueryDelegationTotalRewardsRequest,
QueryDelegationTotalRewardsResponse,
} from 'cosmjs-types/cosmos/distribution/v1beta1/query';
export const useWalletStore = defineStore('walletStore', {
state: () => {
return {
balances: [] as Coin[],
delegations: [] as Delegation[],
unbonding: [] as UnbondingResponses[],
rewards: {total: [], rewards: []} as DelegatorRewards,
wallet: {} as WalletConnected
delegations: [] as DelegationResponse[],
unbonding: [] as UnbondingDelegation[],
rewards: {} as QueryDelegationTotalRewardsResponse,
wallet: {} as WalletConnected,
};
},
getters: {
@ -27,24 +35,24 @@ export const useWalletStore = defineStore('walletStore', {
},
connectedWallet() {
// @ts-ignore
if(this.wallet.cosmosAddress) return this.wallet
if (this.wallet.cosmosAddress) return this.wallet;
const chainStore = useBlockchain();
const key = chainStore.defaultHDPath;
const connected = JSON.parse(localStorage.getItem(key) || '{}');
return connected
return connected;
},
balanceOfStakingToken(): Coin {
const stakingStore = useStakingStore();
return (
this.balances.find(
(x) => x.denom === stakingStore.params.bond_denom
) || { amount: '0', denom: stakingStore.params.bond_denom }
(x) => x.denom === stakingStore.params.bondDenom
) || { amount: '0', denom: stakingStore.params.bondDenom }
);
},
stakingAmount() {
const stakingStore = useStakingStore();
let amt = 0;
let denom = stakingStore.params.bond_denom;
let denom = stakingStore.params.bondDenom;
this.delegations.forEach((i) => {
amt += Number(i.balance.amount);
denom = i.balance.denom;
@ -55,20 +63,20 @@ export const useWalletStore = defineStore('walletStore', {
const stakingStore = useStakingStore();
// @ts-ignore
const reward = this.rewards.total?.find(
(x: Coin) => x.denom === stakingStore.params.bond_denom
(x: Coin) => x.denom === stakingStore.params.bondDenom
);
return reward || { amount: '0', denom: stakingStore.params.bond_denom };
return reward || { amount: '0', denom: stakingStore.params.bondDenom };
},
unbondingAmount() {
let amt = 0;
this.unbonding.forEach((i) => {
this.unbonding?.forEach((i) => {
i.entries.forEach((e) => {
amt += Number(e.balance);
});
});
const stakingStore = useStakingStore();
return { amount: String(amt), denom: stakingStore.params.bond_denom };
return { amount: String(amt), denom: stakingStore.params.bondDenom };
},
currentAddress() {
if (!this.connectedWallet?.cosmosAddress) return '';
@ -77,29 +85,28 @@ export const useWalletStore = defineStore('walletStore', {
return toBech32(chainStore.current?.bech32Prefix || prefix, data);
},
shortAddress() {
const address: string = this.currentAddress
if(address.length > 4) {
return `${address.substring(address.length -4)}`
const address: string = this.currentAddress;
if (address.length > 4) {
return `${address.substring(address.length - 4)}`;
}
return ""
}
return '';
},
},
actions: {
async loadMyAsset() {
if (!this.currentAddress) return;
this.blockchain.rpc.getBankBalances(this.currentAddress).then((x) => {
this.balances = x.balances;
this.balances = x;
});
this.blockchain.rpc
.getStakingDelegations(this.currentAddress)
.then((x) => {
this.delegations = x.delegation_responses;
this.delegations = x.delegationResponses;
});
this.blockchain.rpc
.getStakingDelegatorUnbonding(this.currentAddress)
.then((x) => {
this.unbonding = x.unbonding_responses;
this.unbonding = x.unbondingResponses;
});
this.blockchain.rpc
.getDistributionDelegatorRewards(this.currentAddress)
@ -122,14 +129,14 @@ export const useWalletStore = defineStore('walletStore', {
const chainStore = useBlockchain();
const key = chainStore.defaultHDPath;
localStorage.removeItem(key);
this.$reset()
this.$reset();
},
setConnectedWallet(value: WalletConnected) {
if(value) this.wallet = value
if (value) this.wallet = value;
},
suggestChain() {
// const router = useRouter()
router.push({path: '/wallet/keplr'})
}
router.push({ path: '/wallet/keplr' });
},
},
});
});

View File

@ -3360,31 +3360,7 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cosmjs-types@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.5.2.tgz#2d42b354946f330dfb5c90a87fdc2a36f97b965d"
integrity sha512-zxCtIJj8v3Di7s39uN4LNcN3HIE1z0B9Z0SPE8ZNQR0oSzsuSe1ACgxoFkvhkS7WBasCAFcglS11G2hyfd5tPg==
dependencies:
long "^4.0.0"
protobufjs "~6.11.2"
cosmjs-types@^0.7.1:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.2.tgz#a757371abd340949c5bd5d49c6f8379ae1ffd7e2"
integrity sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA==
dependencies:
long "^4.0.0"
protobufjs "~6.11.2"
cosmjs-types@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.8.0.tgz#2ed78f3e990f770229726f95f3ef5bf9e2b6859b"
integrity sha512-Q2Mj95Fl0PYMWEhA2LuGEIhipF7mQwd9gTQ85DdP9jjjopeoGaDxvmPa5nakNzsq7FnO1DMTatXTAx6bxMH7Lg==
dependencies:
long "^4.0.0"
protobufjs "~6.11.2"
cosmjs-types@^0.9.0:
cosmjs-types@^0.5.2, cosmjs-types@^0.7.1, cosmjs-types@^0.8.0, cosmjs-types@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.9.0.tgz#c3bc482d28c7dfa25d1445093fdb2d9da1f6cfcc"
integrity sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==
@ -5832,7 +5808,7 @@ promised-io@*:
resolved "https://registry.yarnpkg.com/promised-io/-/promised-io-0.3.6.tgz#04c0fea80772f7091dca0f114e30b3e3f7650126"
integrity sha512-bNwZusuNIW4m0SPR8jooSyndD35ggirHlxVl/UhIaZD/F0OBv9ebfc6tNmbpZts3QXHggkjIBH8lvtnzhtcz0A==
protobufjs@^6.11.3, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3:
protobufjs@^6.11.3, protobufjs@^6.8.8, protobufjs@~6.11.3:
version "6.11.4"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa"
integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==