improve account and client
This commit is contained in:
parent
4a494738f9
commit
6a72567342
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)}) },
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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') {
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user