improve account and client

This commit is contained in:
liangping 2023-06-14 18:00:48 +08:00
parent 4a494738f9
commit 6a72567342
6 changed files with 141 additions and 50 deletions

View File

@ -4,13 +4,12 @@ import {
adapter, adapter,
type Request, type Request,
type RequestRegistry, type RequestRegistry,
type Registry,
type AbstractRegistry, type AbstractRegistry,
findApiProfileByChain, findApiProfileByChain,
registryChainProfile, registryChainProfile,
withCustomRequest, withCustomRequest,
} from './registry'; } from './registry';
import { PageRequest } from '@/types'; import { PageRequest,type Coin } from '@/types';
import { CUSTOM } from './custom_api/evmos' import { CUSTOM } from './custom_api/evmos'
export class BaseRestClient<R extends AbstractRegistry> { export class BaseRestClient<R extends AbstractRegistry> {
@ -69,7 +68,8 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
try{ try{
supply = await this.request(this.registry.bank_supply_by_denom, { denom }); supply = await this.request(this.registry.bank_supply_by_denom, { denom });
} catch(err) { } catch(err) {
supply = await this.request({url: "/cosmos/bank/v1beta1/supply/by_denom?denom={denom}", adapter }, { denom }); // will move this to sdk version profile later
supply = await this.request({url: "/cosmos/bank/v1beta1/supply/by_denom?denom={denom}", adapter } as Request<{ amount: Coin }>, { denom });
} }
return supply return supply
} }

View File

@ -1,4 +1,5 @@
import { DEFAULT, type RequestRegistry } from '@/libs' import type{ RequestRegistry } from '@/libs/registry'
import { DEFAULT } from '@/libs'
export const CUSTOM: Partial<RequestRegistry> = { export const CUSTOM: Partial<RequestRegistry> = {
mint_inflation: { url: '/evmos/inflation/v1/inflation_rate', adapter: data => ({inflation: Number(data.inflation_rate || 0)/ 100}) }, mint_inflation: { url: '/evmos/inflation/v1/inflation_rate', adapter: (data: any) => ({inflation: (Number(data.inflation_rate || 0)/ 100 ).toFixed(2)}) },
} }

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { CosmosRestClient } from '@/libs/client'; import { CosmosRestClient } from '@/libs/client';
import { useDashboard, useFormatter } from '@/stores'; import { useDashboard, useFormatter } from '@/stores';
import type { Coin, Delegation } from '@/types'; import type { Coin, CoinWithPrice, Delegation } from '@/types';
import { fromBech32, toBase64, toBech32, toHex } from '@cosmjs/encoding'; import { fromBech32, toBase64, toBech32, toHex } from '@cosmjs/encoding';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { computed } from 'vue'; import { computed } from 'vue';
@ -28,10 +28,11 @@ const conf = ref(
AccountEntry[] AccountEntry[]
> >
); );
const balances = ref({} as Record<string, Coin[]>); const balances = ref({} as Record<string, CoinWithPrice[]>);
const delegations = ref({} as Record<string, Delegation[]>); const delegations = ref({} as Record<string, Delegation[]>);
// load balances // initial loading queue
// load balances
Object.values(conf.value).forEach((imported) => { Object.values(conf.value).forEach((imported) => {
let promise = Promise.resolve(); let promise = Promise.resolve();
for (let i = 0; i < imported.length; i++) { for (let i = 0; i < imported.length; i++) {
@ -53,36 +54,73 @@ Object.values(conf.value).forEach((imported) => {
}); });
const accounts = computed(() => { const accounts = computed(() => {
let a = [] as AccountEntry[]; let a = [] as {
account: AccountEntry;
delegation: CoinWithPrice;
balances: CoinWithPrice[];
}[];
Object.values(conf.value).forEach((x) => { Object.values(conf.value).forEach((x) => {
x.forEach((entry) => { const composition = x.map((entry) => {
const delegation = delegations.value[entry.address]; const d = delegations.value[entry.address];
if (delegation && delegation.length > 0) { let delegation = { } as CoinWithPrice
let amount = 0; if (d && d.length > 0) {
let denom = ''; d.forEach((b) => {
delegation.forEach((b) => { delegation.amount = (Number(b.balance.amount) + Number( delegation.amount || 0)).toFixed()
amount += Number(b.balance.amount); delegation.denom = b.balance.denom;
denom = b.balance.denom;
}); });
entry.delegation = { amount: String(amount), denom }; delegation.value = format.tokenValueNumber(delegation)
} else { delegation.change24h = format.priceChanges(delegation.denom)
entry.delegation = undefined; }
return {
account: entry,
delegation,
balances: balances.value[entry.address]
? balances.value[entry.address].map(x => {
const value = format.tokenValueNumber(x)
return {
amount: x.amount,
denom: x.denom,
value,
change24h: format.priceChanges(x.denom)
}
})
: []
} }
entry.balances = balances.value[entry.address];
}); });
a = a.concat(x); a = a.concat(composition);
}); });
return a; return a;
}); });
const addresses = computed(() => { const addresses = computed(() => {
return accounts.value.map((x) => x.address); return accounts.value.map((x) => x.account.address);
}); });
const totalValue = computed(() => {
return accounts.value.reduce((s, e) => {
s += e.delegation.value || 0
e.balances.forEach(b => {
s += b.value || 0
})
return s
}, 0)
})
const totalChange= computed(() => {
return accounts.value.reduce((s, e) => {
s += (e.delegation.change24h || 0) * (e.delegation.value || 0) / 100
e.balances.forEach(b => {
s += (b.change24h || 0) * (b.value || 0) / 100
})
return s
}, 0)
})
// Adding Model Boxes
const sourceOptions = computed(() => { const sourceOptions = computed(() => {
// scan all connected wallet // scan all connected wallet
const keys = scanLocalKeys(); const keys = scanLocalKeys();
// all existed keys // parser options from all existed keys
Object.values(conf.value).forEach((x) => { Object.values(conf.value).forEach((x) => {
const [first] = x; const [first] = x;
if (first) { if (first) {
@ -100,7 +138,7 @@ const sourceOptions = computed(() => {
} }
} }
}); });
// address // parse options from an given address
if (sourceAddress.value) { if (sourceAddress.value) {
const { prefix, data } = fromBech32(sourceAddress.value); const { prefix, data } = fromBech32(sourceAddress.value);
const chain = Object.values(dashboard.chains).find( const chain = Object.values(dashboard.chains).find(
@ -128,6 +166,8 @@ const availableAccount = computed(() => {
return []; return [];
}); });
// helper functions
// remove address from the list
function removeAddress(addr: string) { function removeAddress(addr: string) {
const newConf = {} as Record<string, AccountEntry[]>; const newConf = {} as Record<string, AccountEntry[]>;
Object.keys(conf.value).forEach((key) => { Object.keys(conf.value).forEach((key) => {
@ -138,6 +178,7 @@ function removeAddress(addr: string) {
localStorage.setItem('imported-addresses', JSON.stringify(conf.value)); localStorage.setItem('imported-addresses', JSON.stringify(conf.value));
} }
// add address to the local list
async function addAddress(acc: AccountEntry) { async function addAddress(acc: AccountEntry) {
const { data } = fromBech32(acc.address); const { data } = fromBech32(acc.address);
const key = toBase64(data); const key = toBase64(data);
@ -168,6 +209,7 @@ async function addAddress(acc: AccountEntry) {
localStorage.setItem('imported-addresses', JSON.stringify(conf.value)); localStorage.setItem('imported-addresses', JSON.stringify(conf.value));
} }
// load balances for an address
async function loadBalances(endpoint: string, address: string) { async function loadBalances(endpoint: string, address: string) {
const client = CosmosRestClient.newDefault(endpoint); const client = CosmosRestClient.newDefault(endpoint);
await client.getBankBalances(address).then((res) => { await client.getBankBalances(address).then((res) => {
@ -213,7 +255,11 @@ async function loadBalances(endpoint: string, address: string) {
</div> </div>
</div> </div>
</div> </div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0"></div> <div class="mt-5 flex flex-col lg:!ml-4 lg:!mt-0 text-right">
<span>Total Value</span>
<span class="text-xl text-success font-bold">${{ format.formatNumber(totalValue, '0,0.[00]') }}</span>
<span class="text-sm" :class="format.color(totalChange)">{{ format.formatNumber(totalChange, '+0,0.[00]') }}</span>
</div>
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table table-compact w-full"> <table class="table table-compact w-full">
@ -229,38 +275,38 @@ async function loadBalances(endpoint: string, address: string) {
</thead> </thead>
<tbody> <tbody>
<!-- row 1 --> <!-- row 1 -->
<tr v-for="acc in accounts"> <tr v-for="{account, balances, delegation} in accounts">
<td v-if="editable"> <td v-if="editable">
<Icon <Icon
icon="mdi:close-box" icon="mdi:close-box"
class="text-error" class="text-error"
@click="removeAddress(acc.address)" @click="removeAddress(account.address)"
></Icon> ></Icon>
</td> </td>
<td class="px-4"> <td class="px-4">
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`"> <RouterLink :to="`/${account.chainName}/account/${account.address}`">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<div class="avatar"> <div class="avatar">
<div class="mask mask-squircle w-8 h-8"> <div class="mask mask-squircle w-8 h-8">
<img :src="acc.logo" :alt="acc.address" /> <img :src="account.logo" :alt="account.address" />
</div> </div>
</div> </div>
<div> <div>
<div class="font-bold capitalize"> <div class="font-bold capitalize">
{{ acc.chainName }} {{ account.chainName }}
</div> </div>
<div class="text-sm opacity-50 hidden md:!block"> <div class="text-sm opacity-50 hidden md:!block">
{{ acc.address }} {{ account.address }}
</div> </div>
</div> </div>
</div> </div>
</RouterLink> </RouterLink>
</td> </td>
<td> <td>
<div v-if="acc.delegation"> <div v-if="delegation">
{{ {{
format.formatToken( format.formatToken(
acc.delegation, delegation,
true, true,
'0,0.[0000]', '0,0.[0000]',
'all' 'all'
@ -268,23 +314,23 @@ async function loadBalances(endpoint: string, address: string) {
}} }}
<div <div
class="text-xs" class="text-xs"
:class="format.priceColor(acc.delegation.denom)" :class="format.priceColor(delegation.denom)"
> >
${{ format.tokenValue(acc.delegation) }} ${{ format.formatNumber(delegation.value, '0,0.[00]') }}
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div class="flex"> <ul tabindex="0" >
<span v-for="b in acc.balances" class="mr-1"> <li v-for="b in balances">
{{ format.formatToken(b, true, '0,0.[0000]', 'all') }} {{ format.formatToken(b, true, '0,0.[0000]', 'all') }}
<div class="text-xs" :class="format.priceColor(b.denom)"> <div class="text-xs" :class="format.priceColor(b.denom)">
${{ format.tokenValue(b) }} ({{ ${{ format.formatNumber(b.value, '0,0.[00]') }} ({{
format.showChanges(format.priceChanges(b.denom)) format.formatNumber(b.change24h, '+0.[0]')
}}%) }}%)
</div> </div>
</span> </li>
</div> </ul>
</td> </td>
<th> <th>
<button class="btn btn-ghost btn-xs hidden">details</button> <button class="btn btn-ghost btn-xs hidden">details</button>
@ -332,7 +378,7 @@ async function loadBalances(endpoint: string, address: string) {
Import Import
</a> </a>
</span> </span>
<RouterLink to="/wallet/keplr" class="btn btn-sm"> <RouterLink to="/wallet/keplr" class="btn btn-sm btn-primary">
Add chain to Keplr Add chain to Keplr
</RouterLink> </RouterLink>
</div> </div>

View File

@ -8,8 +8,6 @@ export interface AccountEntry {
address: string, address: string,
coinType: string, coinType: string,
endpoint?: string, endpoint?: string,
delegation?: Coin,
balances?: Coin[],
compatiable?: boolean, compatiable?: boolean,
} }

View File

@ -73,8 +73,7 @@ export const useFormatter = defineStore('formatter', {
const prices = this.dashboard.prices[id]; const prices = this.dashboard.prices[id];
return prices; return prices;
}, },
priceColor(denom: string, currency = "usd") { color(change: number) {
const change = this.priceChanges(denom, currency)
switch (true) { switch (true) {
case change > 0: case change > 0:
return "text-success" return "text-success"
@ -84,6 +83,10 @@ export const useFormatter = defineStore('formatter', {
return "" return ""
} }
}, },
priceColor(denom: string, currency = "usd") {
const change = this.priceChanges(denom, currency)
return this.color(change)
},
price(denom: string, currency = "usd") { price(denom: string, currency = "usd") {
if(!denom || denom.length < 2) return 0 if(!denom || denom.length < 2) return 0
const info = this.priceInfo(denom); const info = this.priceInfo(denom);
@ -93,7 +96,7 @@ export const useFormatter = defineStore('formatter', {
const info = this.priceInfo(denom); const info = this.priceInfo(denom);
return info ? info[`${currency}_24h_change`] || 0 : 0; return info ? info[`${currency}_24h_change`] || 0 : 0;
}, },
showChanges(v: number) { showChanges(v?: number) {
return v!==0 ? numeral(v).format("+0,0.[00]"): "" return v!==0 ? numeral(v).format("+0,0.[00]"): ""
}, },
tokenValue(token?: Coin) { tokenValue(token?: Coin) {
@ -138,6 +141,42 @@ export const useFormatter = defineStore('formatter', {
} }
return null return null
}, },
tokenDisplayNumber(
token?: { denom: string; amount: string },
mode = 'all'
) {
if (token && token.amount && token?.denom) {
let amount = Number(token.amount);
let denom = token.denom;
if (denom && denom.startsWith('ibc/')) {
let ibcDenom = this.ibcDenoms[denom.replace('ibc/', '')];
if (ibcDenom) {
denom = ibcDenom.base_denom;
}
}
const 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 (conf) {
let unit = { exponent: 6, denom: '' };
// find the max exponent for display
conf.denom_units.forEach((x) => {
if (x.exponent >= unit.exponent) {
unit = x;
}
});
if (unit && unit.exponent > 0) {
amount = amount / Math.pow(10, unit.exponent || 6);
}
}
return amount;
}
return 0;
},
formatToken( formatToken(
token?: { denom: string; amount: string }, token?: { denom: string; amount: string },
withDenom = true, withDenom = true,
@ -230,7 +269,8 @@ export const useFormatter = defineStore('formatter', {
percent(decimal?: string | number) { percent(decimal?: string | number) {
return decimal ? numeral(decimal).format('0.[00]%') : '-'; return decimal ? numeral(decimal).format('0.[00]%') : '-';
}, },
formatNumber(input: number, fmt = '0.[00]') { formatNumber(input?: number, fmt = '0.[00]') {
if(!input) return ""
return numeral(input).format(fmt) return numeral(input).format(fmt)
}, },
numberAndSign(input: number, fmt = '+0,0') { numberAndSign(input: number, fmt = '+0,0') {

View File

@ -52,6 +52,12 @@ export interface Coin {
denom: string; denom: string;
} }
export interface CoinWithPrice extends Coin {
value?: number;
price?: number;
change24h?: number
}
export interface UptimeStatus { export interface UptimeStatus {
height: number; height: number;
filled: boolean; filled: boolean;