feat: h5 style

This commit is contained in:
Alisa | Side.one 2023-05-29 18:04:07 +08:00
parent ee27d61e1e
commit a57e8e62f6
2 changed files with 406 additions and 282 deletions

View File

@ -6,132 +6,155 @@ 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';
import { ref } from 'vue'; import { ref } from 'vue';
import { scanLocalKeys, type AccountEntry, scanCompatibleAccounts, type LocalKey } from './utils'; import {
scanLocalKeys,
type AccountEntry,
scanCompatibleAccounts,
type LocalKey,
} from './utils';
const dashboard = useDashboard() const dashboard = useDashboard();
const format = useFormatter() const format = useFormatter();
const editable = ref(false) // to edit addresses const editable = ref(false); // to edit addresses
const sourceAddress = ref('') // const sourceAddress = ref(''); //
const selectedSource = ref({} as LocalKey) // const selectedSource = ref({} as LocalKey); //
function toggleEdit() { function toggleEdit() {
editable.value = !editable.value editable.value = !editable.value;
} }
const conf = ref(JSON.parse(localStorage.getItem("imported-addresses") || "{}") as Record<string, AccountEntry[]>) const conf = ref(
const balances = ref({} as Record<string, Coin[]>) JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
const delegations = ref({} as Record<string, Delegation[]>) string,
AccountEntry[]
>
);
const balances = ref({} as Record<string, Coin[]>);
const delegations = ref({} as Record<string, Delegation[]>);
// load balances // 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++) {
promise = promise.then(() => new Promise((resolve) => { promise = promise.then(
// continue only if the page is living () =>
if (imported[i].endpoint) { new Promise((resolve) => {
loadBalances(imported[i].endpoint || "", imported[i].address).finally(() => resolve()) // continue only if the page is living
} else { if (imported[i].endpoint) {
resolve() loadBalances(
} imported[i].endpoint || '',
})) imported[i].address
).finally(() => resolve());
} else {
resolve();
}
})
);
} }
});
})
const accounts = computed(() => { const accounts = computed(() => {
let a = [] as AccountEntry[] let a = [] as AccountEntry[];
Object.values(conf.value).forEach(x => { Object.values(conf.value).forEach((x) => {
x.forEach(entry => { x.forEach((entry) => {
const delegation = delegations.value[entry.address] const delegation = delegations.value[entry.address];
if (delegation && delegation.length > 0) { if (delegation && delegation.length > 0) {
let amount = 0 let amount = 0;
let denom = "" let denom = '';
delegation.forEach(b => { delegation.forEach((b) => {
amount += Number(b.balance.amount) amount += Number(b.balance.amount);
denom = b.balance.denom denom = b.balance.denom;
}) });
entry.delegation = { amount: String(amount), denom } entry.delegation = { amount: String(amount), denom };
} else { } else {
entry.delegation = undefined entry.delegation = undefined;
} }
entry.balances = balances.value[entry.address] entry.balances = balances.value[entry.address];
}) });
a = a.concat(x) a = a.concat(x);
}) });
return a return a;
}) });
const addresses = computed(() => { const addresses = computed(() => {
return accounts.value.map(x => (x.address)) return accounts.value.map((x) => x.address);
}) });
const sourceOptions = computed(() => { const sourceOptions = computed(() => {
// scan all connected wallet // scan all connected wallet
const keys = scanLocalKeys() const keys = scanLocalKeys();
// all existed keys // 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) {
const { data } = fromBech32(first.address) const { data } = fromBech32(first.address);
const hex = toHex(data) const hex = toHex(data);
if (keys.findIndex(k => toHex(fromBech32(k.cosmosAddress).data) === hex) === -1) { if (
keys.findIndex(
(k) => toHex(fromBech32(k.cosmosAddress).data) === hex
) === -1
) {
keys.push({ keys.push({
cosmosAddress: first.address, cosmosAddress: first.address,
hdPath: `m/44/${first.coinType}/0'/0/0` hdPath: `m/44/${first.coinType}/0'/0/0`,
}) });
} }
} }
}) });
// address // 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(x => x.bech32Prefix === prefix) const chain = Object.values(dashboard.chains).find(
(x) => x.bech32Prefix === prefix
);
if (chain) { if (chain) {
keys.push({ keys.push({
cosmosAddress: sourceAddress.value, cosmosAddress: sourceAddress.value,
hdPath: `m/44/${chain.coinType}/0'/0/0` hdPath: `m/44/${chain.coinType}/0'/0/0`,
}) });
} }
} }
if (!selectedSource.value.cosmosAddress && keys.length > 0) { if (!selectedSource.value.cosmosAddress && keys.length > 0) {
selectedSource.value = keys[0] selectedSource.value = keys[0];
} }
return keys return keys;
}) });
const availableAccount = computed(() => { const availableAccount = computed(() => {
if (selectedSource.value.cosmosAddress) { if (selectedSource.value.cosmosAddress) {
return scanCompatibleAccounts([selectedSource.value]).filter(x => !addresses.value.includes(x.address)) return scanCompatibleAccounts([selectedSource.value]).filter(
(x) => !addresses.value.includes(x.address)
);
} }
return [] return [];
}) });
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) => {
const acc = conf.value[key].filter(x => x.address !== addr) const acc = conf.value[key].filter((x) => x.address !== addr);
if (acc.length > 0) newConf[key] = acc if (acc.length > 0) newConf[key] = acc;
}) });
conf.value = newConf conf.value = newConf;
localStorage.setItem("imported-addresses", JSON.stringify(conf.value)) localStorage.setItem('imported-addresses', JSON.stringify(conf.value));
} }
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);
if (conf.value[key]) { if (conf.value[key]) {
// existed // existed
if (conf.value[key].findIndex(x => x.address === acc.address) > -1) { if (conf.value[key].findIndex((x) => x.address === acc.address) > -1) {
return return;
} }
conf.value[key].push(acc) conf.value[key].push(acc);
} else { } else {
conf.value[key] = [acc] conf.value[key] = [acc];
} }
// also add chain to favorite // also add chain to favorite
if (!dashboard?.favoriteMap?.[acc.chainName]) { if (!dashboard?.favoriteMap?.[acc.chainName]) {
dashboard.favoriteMap[acc.chainName] = true dashboard.favoriteMap[acc.chainName] = true;
window.localStorage.setItem( window.localStorage.setItem(
'favoriteMap', 'favoriteMap',
JSON.stringify(dashboard.favoriteMap) JSON.stringify(dashboard.favoriteMap)
@ -139,153 +162,209 @@ async function addAddress(acc: AccountEntry) {
} }
if (acc.endpoint) { if (acc.endpoint) {
loadBalances(acc.endpoint, acc.address) loadBalances(acc.endpoint, acc.address);
} }
localStorage.setItem("imported-addresses", JSON.stringify(conf.value)) localStorage.setItem('imported-addresses', JSON.stringify(conf.value));
} }
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) => {
balances.value[address] = res.balances.filter(x => x.denom.length < 10) balances.value[address] = res.balances.filter((x) => x.denom.length < 10);
}) });
await client.getStakingDelegations(address).then(res => { await client.getStakingDelegations(address).then((res) => {
delegations.value[address] = res.delegation_responses delegations.value[address] = res.delegation_responses;
}) });
} }
</script> </script>
<template> <template>
<div> <div>
<div class="overflow-x-auto w-full card "> <div class="overflow-x-auto w-full card">
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5"> <div
class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5"
>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">Accounts</h2> <h2
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"> class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Accounts
</h2>
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div class="mt-2 flex items-center text-sm text-gray-500"> <div class="mt-2 flex items-center text-sm text-gray-500">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor" <svg
aria-hidden="true"> class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
<path fill-rule="evenodd" viewBox="0 0 20 20"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z" fill="currentColor"
clip-rule="evenodd" /> aria-hidden="true"
>
<path <path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" /> fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd"
/>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
/>
</svg> </svg>
Manage all your assets in one page Manage all your assets in one page
</div> </div>
</div> </div>
</div> </div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0"> <div class="mt-5 flex lg:!ml-4 lg:!mt-0"></div>
</div>
</div> </div>
<div class="overflow-x-auto">
<table class="table table-compact w-full"> <table class="table table-compact w-full">
<!-- head --> <!-- head -->
<thead class="rounded-none"> <thead class="rounded-none">
<tr> <tr>
<th v-if="editable"></th> <th v-if="editable"></th>
<th>Account</th> <th>Account</th>
<th>Delegation</th> <th>Delegation</th>
<th>Balance</th> <th>Balance</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<!-- row 1 --> <!-- row 1 -->
<tr v-for="acc in accounts"> <tr v-for="acc in accounts">
<td v-if="editable"> <td v-if="editable">
<Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon> <Icon
</td> icon="mdi:close-box"
<td class="px-4"> class="text-error"
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`"> @click="removeAddress(acc.address)"
<div class="flex items-center space-x-2"> ></Icon>
<div class="avatar"> </td>
<div class="mask mask-squircle w-8 h-8"> <td class="px-4">
<img :src="acc.logo" :alt="acc.address" /> <RouterLink :to="`/${acc.chainName}/account/${acc.address}`">
<div class="flex items-center space-x-2">
<div class="avatar">
<div class="mask mask-squircle w-8 h-8">
<img :src="acc.logo" :alt="acc.address" />
</div>
</div>
<div>
<div class="font-bold capitalize">
{{ acc.chainName }}
</div>
<div class="text-sm opacity-50 hidden md:!block">
{{ acc.address }}
</div>
</div> </div>
</div> </div>
<div> </RouterLink>
<div class="font-bold capitalize">{{ acc.chainName }}</div> </td>
<div class="text-sm opacity-50 hidden md:!block">{{ acc.address }}</div> <td>
<div v-if="acc.delegation">
{{
format.formatToken(
acc.delegation,
true,
'0,0.[0000]',
'all'
)
}}
<div
class="text-xs"
:class="format.priceColor(acc.delegation.denom)"
>
${{ format.tokenValue(acc.delegation) }}
</div> </div>
</div> </div>
</RouterLink> </td>
</td> <td>
<td> <div class="flex">
<div v-if="acc.delegation"> <span v-for="b in acc.balances" class="mr-1">
{{ format.formatToken(acc.delegation, true, '0,0.[0000]', 'all') }} {{ format.formatToken(b, true, '0,0.[0000]', 'all') }}
<div class="text-xs" :class="format.priceColor(acc.delegation.denom)">${{ <div class="text-xs" :class="format.priceColor(b.denom)">
format.tokenValue(acc.delegation) }}</div> ${{ format.tokenValue(b) }} ({{
</div> format.showChanges(format.priceChanges(b.denom))
</td> }}%)
<td> </div>
<div class="flex"> </span>
<span v-for="b in acc.balances" class="mr-1"> </div>
{{ format.formatToken(b, true, '0,0.[0000]', 'all') }} </td>
<div class="text-xs" :class="format.priceColor(b.denom)">${{ format.tokenValue(b) }} ({{ <th>
format.showChanges(format.priceChanges(b.denom)) }}%)</div> <button class="btn btn-ghost btn-xs hidden">details</button>
</th>
</tr>
</tbody>
<tfoot>
<th colspan="10">
<div class="flex justify-between">
<span class="hidden sm:!block">
<button
type="button"
class="inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm"
@click="toggleEdit"
>
<svg
class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z"
/>
</svg>
Edit
</button>
<a
href="#address-modal"
class="inline-flex items-center ml-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
<svg
class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z"
/>
<path
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z"
/>
</svg>
Import
</a>
</span> </span>
<RouterLink to="/wallet/keplr" class="btn btn-sm">
Add chain to Keplr
</RouterLink>
</div> </div>
</td>
<th>
<button class="btn btn-ghost btn-xs hidden">details</button>
</th> </th>
</tr> </tfoot>
</tbody> </table>
<tfoot> </div>
<th colspan="10">
<div class="flex justify-between">
<span class="hidden sm:!block">
<button type="button"
class="inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm"
@click="toggleEdit">
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path
d="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z" />
</svg>
Edit
</button>
<a href="#address-modal"
class="inline-flex items-center ml-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z" />
<path
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z" />
</svg>
Import
</a>
</span>
<RouterLink to="/wallet/keplr" class="btn btn-sm"> Add chain to Keplr </RouterLink>
</div>
</th>
</tfoot>
</table>
</div> </div>
<!-- Put this part before </body> tag --> <!-- Put this part before </body> tag -->
<div class="modal" id="address-modal"> <div class="modal" id="address-modal">
<div class="modal-box"> <div class="modal-box">
<h3 class="font-bold text-lg mb-2">Derive Account From Address <h3 class="font-bold text-lg mb-2">Derive Account From Address</h3>
</h3>
<div> <div>
<label class="input-group input-group-sm w-full"> <label class="input-group input-group-sm w-full">
<span>Connected</span> <span>Connected</span>
<select v-model="selectedSource" class="select select-bordered select-sm w-3/4"> <select
v-model="selectedSource"
class="select select-bordered select-sm w-3/4"
>
<option v-for="source in sourceOptions" :value="source"> <option v-for="source in sourceOptions" :value="source">
<span class=" overflow-hidden">{{ source.cosmosAddress }}</span> <span class="overflow-hidden">{{ source.cosmosAddress }}</span>
</option> </option>
</select> </select>
</label> </label>
<label class="input-group input-group-sm my-2"> <label class="input-group input-group-sm my-2">
<span>Custom</span> <span>Custom</span>
<input v-model="sourceAddress" class="input input-bordered w-full input-sm" placeholder="Input an address" /> <input
v-model="sourceAddress"
class="input input-bordered w-full input-sm"
placeholder="Input an address"
/>
</label> </label>
</div> </div>
<div class="py-4 max-h-72 overflow-y-auto"> <div class="py-4 max-h-72 overflow-y-auto">
@ -299,18 +378,33 @@ async function loadBalances(endpoint: string, address: string) {
</div> </div>
</div> </div>
<div> <div>
<div class="tooltip" :class="acc.compatiable ? 'tooltip-success' : 'tooltip-error'" <div
:data-tip="`Coin Type: ${acc.coinType}`"> class="tooltip"
<div class="font-bold capitalize" :class="acc.compatiable ? 'text-green-500' : 'text-red-500'"> :class="
acc.compatiable ? 'tooltip-success' : 'tooltip-error'
"
:data-tip="`Coin Type: ${acc.coinType}`"
>
<div
class="font-bold capitalize"
:class="
acc.compatiable ? 'text-green-500' : 'text-red-500'
"
>
{{ acc.chainName }} {{ acc.chainName }}
</div> </div>
</div> </div>
<div class="text-xs opacity-50 hidden md:!block">{{ acc.address }}</div> <div class="text-xs opacity-50 hidden md:!block">
{{ acc.address }}
</div>
</div> </div>
</div> </div>
</td> </td>
<td class="text-right"> <td class="text-right">
<span class="btn !bg-yes !border-yes btn-xs text-white" @click="addAddress(acc)"> <span
class="btn !bg-yes !border-yes btn-xs text-white"
@click="addAddress(acc)"
>
<Icon icon="mdi:plus" /> <Icon icon="mdi:plus" />
</span> </span>
</td> </td>

View File

@ -8,137 +8,167 @@ import { computed } from 'vue';
import { useFormatter } from '@/stores'; import { useFormatter } from '@/stores';
import DonutChart from '@/components/charts/DonutChart.vue'; import DonutChart from '@/components/charts/DonutChart.vue';
const format = useFormatter() const format = useFormatter();
const conf = ref(JSON.parse(localStorage.getItem("imported-addresses") || "{}") as Record<string, AccountEntry[]>) const conf = ref(
const balances = ref({} as Record<string, Coin[]>) JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
const delegations = ref({} as Record<string, Delegation[]>) string,
const tokenMeta = ref({} as Record<string, AccountEntry>) AccountEntry[]
>
);
const balances = ref({} as Record<string, Coin[]>);
const delegations = ref({} as Record<string, Delegation[]>);
const tokenMeta = ref({} as Record<string, AccountEntry>);
scanLocalKeys().forEach(wallet => { scanLocalKeys().forEach((wallet) => {
const { data } = fromBech32(wallet.cosmosAddress) const { data } = fromBech32(wallet.cosmosAddress);
const walletKey = toBase64(data) const walletKey = toBase64(data);
let imported = conf.value[walletKey] let imported = conf.value[walletKey];
// load balance & delegations // load balance & delegations
if (imported) imported.forEach(x => { if (imported)
if (x.endpoint) { imported.forEach((x) => {
const client = CosmosRestClient.newDefault(x.endpoint) if (x.endpoint) {
client.getBankBalances(x.address).then(res => { const client = CosmosRestClient.newDefault(x.endpoint);
const bal = res.balances.filter(x => x.denom.length < 10) client.getBankBalances(x.address).then((res) => {
balances.value[x.address] = bal const bal = res.balances.filter((x) => x.denom.length < 10);
bal.forEach(b => { balances.value[x.address] = bal;
tokenMeta.value[b.denom] = x bal.forEach((b) => {
}) tokenMeta.value[b.denom] = x;
}) });
client.getStakingDelegations(x.address).then(res => { });
delegations.value[x.address] = res.delegation_responses client.getStakingDelegations(x.address).then((res) => {
res.delegation_responses.forEach(del => { delegations.value[x.address] = res.delegation_responses;
tokenMeta.value[del.balance.denom] = x res.delegation_responses.forEach((del) => {
}) tokenMeta.value[del.balance.denom] = x;
}) });
} });
}) }
}) });
});
const tokenValues = computed(() => { const tokenValues = computed(() => {
const values = {} as Record<string, number> const values = {} as Record<string, number>;
Object.values(balances.value).forEach(b => { Object.values(balances.value).forEach((b) => {
b.forEach(coin => { b.forEach((coin) => {
const v = format.tokenValueNumber(coin) const v = format.tokenValueNumber(coin);
if(v) { if (v) {
if (values[coin.denom]) { if (values[coin.denom]) {
values[coin.denom] += v values[coin.denom] += v;
} else { } else {
values[coin.denom] = v values[coin.denom] = v;
} }
} }
}) });
}) });
Object.values(delegations.value).forEach(b => { Object.values(delegations.value).forEach((b) => {
b.forEach(d => { b.forEach((d) => {
const v = format.tokenValueNumber(d.balance) const v = format.tokenValueNumber(d.balance);
if(v) { if (v) {
if (values[d.balance.denom]) { if (values[d.balance.denom]) {
values[d.balance.denom] += v values[d.balance.denom] += v;
} else { } else {
values[d.balance.denom] = v values[d.balance.denom] = v;
} }
} }
}) });
}) });
return values return values;
}) });
const totalValue = computed(() => { const totalValue = computed(() => {
return Object.values(tokenValues.value).reduce((a, s) => (a + s), 0) return Object.values(tokenValues.value).reduce((a, s) => a + s, 0);
}) });
const tokenList = computed(() => { const tokenList = computed(() => {
const list = [] as { denom: string, value: number, logo: string, chainName: string, percentage: number }[] const list = [] as {
Object.keys(tokenValues.value).map(x => { denom: string;
value: number;
logo: string;
chainName: string;
percentage: number;
}[];
Object.keys(tokenValues.value).map((x) => {
list.push({ list.push({
denom: x, denom: x,
value: tokenValues.value[x], value: tokenValues.value[x],
chainName: tokenMeta.value[x]?.chainName, chainName: tokenMeta.value[x]?.chainName,
logo: tokenMeta.value[x]?.logo, logo: tokenMeta.value[x]?.logo,
percentage: tokenValues.value[x] / totalValue.value percentage: tokenValues.value[x] / totalValue.value,
}) });
}) });
return list.filter(x => x.value > 0).sort((a, b) => b.value - a.value) return list.filter((x) => x.value > 0).sort((a, b) => b.value - a.value);
}) });
</script> </script>
<template> <template>
<div class="overflow-x-auto w-full card "> <div class="overflow-x-auto w-full card">
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5"> <div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5">
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">Portfolio</h2> <h2
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"> class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Portfolio
</h2>
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div class="mt-2 flex items-center text-sm text-gray-500"> <div class="mt-2 flex items-center text-sm text-gray-500">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor" <svg
aria-hidden="true"> class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
<path fill-rule="evenodd" viewBox="0 0 20 20"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z" fill="currentColor"
clip-rule="evenodd" /> aria-hidden="true"
>
<path <path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" /> fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd"
/>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
/>
</svg> </svg>
Manage all your assets in one page {{ totalValue }} Manage all your assets in one page {{ totalValue }}
</div> </div>
</div> </div>
</div> </div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0"> <div class="mt-5 flex lg:!ml-4 lg:!mt-0"></div>
</div>
</div> </div>
<div class="bg-base-100"> <div class="bg-base-100">
<DonutChart :series="Object.values(tokenValues)" :labels="Object.keys(tokenValues)"/> <DonutChart
<table class="table w-full"> :series="Object.values(tokenValues)"
<thead> :labels="Object.keys(tokenValues)"
<tr> />
<th>Token</th> <div class="overflow-x-auto">
<th class="text-right">Value</th> <table class="table w-full">
<th class="text-right">Percent</th> <thead>
</tr> <tr>
</thead> <th>Token</th>
<tbody> <th class="text-right">Value</th>
<tr v-for="x in tokenList"> <th class="text-right">Percent</th>
<td class="capitalize"> </tr>
<div class="flex"> </thead>
<div class="avatar"> <tbody>
<div class="mask mask-squircle w-6 h-6 mr-2"> <tr v-for="(x, index) in tokenList" :key="index">
<img :src="x.logo" :alt="x.chainName" /> <td class="capitalize">
<div class="flex">
<div class="avatar">
<div class="mask mask-squircle w-6 h-6 mr-2">
<img :src="x.logo" :alt="x.chainName" />
</div>
</div> </div>
{{ x.chainName }}
</div> </div>
{{ x.chainName }} </td>
</div> <td class="text-right">${{ format.formatNumber(x.value) }}</td>
</td> <td class="text-right">{{ format.percent(x.percentage) }}</td>
<td class="text-right">${{ format.formatNumber(x.value) }}</td> </tr>
<td class="text-right">{{ format.percent(x.percentage) }}</td> </tbody>
</tr> </table>
</tbody>
</table> </div>
<div class="p-4 text-center" v-show="tokenList?.length ===0">
No Data
</div>
</div> </div>
</div> </div>
</template> </template>