Merge remote-tracking branch 'upstream/master' into v3-single

This commit is contained in:
Alisa | Side.one 2023-05-28 14:51:12 +08:00
commit 5876ca7343
8 changed files with 169 additions and 113 deletions

BIN
public/logos/ledger.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/logos/ledger.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

View File

@ -85,6 +85,8 @@ function changeEndpoint(item: Endpoint) {
Height: {{ baseStore.latest.block?.header.height }} Height: {{ baseStore.latest.block?.header.height }}
</div> </div>
</div> </div>
<!-- bottom-->
<div class="px-4 py-2">&nbsp;</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -37,7 +37,7 @@ const tipMsg = computed(() => {
<div class="dropdown dropdown-hover dropdown-end"> <div class="dropdown dropdown-hover dropdown-end">
<label <label
tabindex="0" tabindex="0"
class="btn btn-sm m-1 lowercase hidden truncate md:!inline-flex text-xs md:!text-sm" class="btn btn-sm btn-primary m-1 lowercase hidden truncate md:!inline-flex text-xs md:!text-sm"
> >
<Icon icon="mdi:wallet" /> <Icon icon="mdi:wallet" />
<span class="ml-1 hidden md:block"> <span class="ml-1 hidden md:block">
@ -51,7 +51,7 @@ const tipMsg = computed(() => {
<label <label
v-if="!walletStore?.currentAddress" v-if="!walletStore?.currentAddress"
for="PingConnectWallet" for="PingConnectWallet"
class="btn btn-sm" class="btn btn-sm btn-primary"
><Icon icon="mdi:wallet" /><span class="ml-1 hidden md:block" ><Icon icon="mdi:wallet" /><span class="ml-1 hidden md:block"
>Connect Wallet</span >Connect Wallet</span
></label ></label

View File

@ -321,19 +321,21 @@ const color = computed(() => {
</Teleport> </Teleport>
</div> </div>
<div class="bg-base-100 rounded mt-4 shadow"> <div class="bg-base-100 rounded mt-4">
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main"> <div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
Application Versions Application Versions
</div> </div>
<!-- Application Version --> <!-- Application Version -->
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" /> <ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
<div class="h-4"></div>
</div> </div>
<div v-if="!store.coingeckoId" class="bg-base-100 rounded mt-4 shadow"> <div v-if="!store.coingeckoId" class="bg-base-100 rounded mt-4">
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main"> <div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
Node Information Node Information
</div> </div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" /> <ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
<div class="h-4"></div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -2,15 +2,17 @@
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, Delegation } from '@/types';
import { fromBech32, toBase64, toBech32 } 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';
import { ref } from 'vue'; import { ref } from 'vue';
import { scanLocalKeys, type AccountEntry, scanCompatibleAccounts } 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 selectedSource = ref({} as LocalKey) //
function toggleEdit() { function toggleEdit() {
editable.value = !editable.value editable.value = !editable.value
} }
@ -19,42 +21,21 @@ const conf = ref(JSON.parse(localStorage.getItem("imported-addresses") || "{}")
const balances = ref({} as Record<string, Coin[]>) const balances = ref({} as Record<string, Coin[]>)
const delegations = ref({} as Record<string, Delegation[]>) const delegations = ref({} as Record<string, Delegation[]>)
scanLocalKeys().forEach(wallet => { // load balances
const { data } = fromBech32(wallet.cosmosAddress) Object.values(conf.value).forEach(imported => {
const walletKey = toBase64(data) let promise = Promise.resolve()
let imported = conf.value[walletKey] for(let i = 0;i < imported.length; i++) {
// save the default address to local storage promise = promise.then(() => new Promise((resolve) => {
if (!imported) { // continue only if the page is living
imported = [] if(imported[i].endpoint) {
dashboard.favorite.forEach(x => { loadBalances(imported[i].endpoint || "", imported[i].address).finally(()=> resolve())
const chain = dashboard.chains[x] } else {
if (chain && wallet.hdPath.indexOf(chain.coinType) === 6) { resolve()
imported.push({
chainName: chain.chainName,
logo: chain.logo,
address: toBech32(chain.bech32Prefix, data),
coinType: chain.coinType,
endpoint: chain.endpoints.rest?.at(0)?.address
})
} }
}) }))
conf.value[walletKey] = imported;
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
} }
// load balance & delegations
imported.forEach(x => {
if (x.endpoint) {
const client = CosmosRestClient.newDefault(x.endpoint)
client.getBankBalances(x.address).then(res => {
balances.value[x.address] = res.balances.filter(x => x.denom.length < 10)
})
client.getStakingDelegations(x.address).then(res => {
delegations.value[x.address] = res.delegation_responses
})
}
})
})
})
const accounts = computed(() => { const accounts = computed(() => {
let a = [] as AccountEntry[] let a = [] as AccountEntry[]
@ -69,6 +50,8 @@ const accounts = computed(() => {
denom = b.balance.denom denom = b.balance.denom
}) })
entry.delegation = { amount: String(amount), denom } entry.delegation = { amount: String(amount), denom }
}else{
entry.delegation = undefined
} }
entry.balances = balances.value[entry.address] entry.balances = balances.value[entry.address]
}) })
@ -81,43 +64,97 @@ const addresses = computed(() => {
return accounts.value.map(x => (x.address)) return accounts.value.map(x => (x.address))
}) })
const sourceOptions = computed(() => {
// scan all connected wallet
const keys = scanLocalKeys()
// all existed keys
Object.values(conf.value).forEach(x => {
const [first] = x
if (first) {
const { data } = fromBech32(first.address)
const hex = toHex(data)
if (keys.findIndex(k => toHex(fromBech32(k.cosmosAddress).data) === hex) === -1) {
keys.push({
cosmosAddress: first.address,
hdPath: `m/44/${first.coinType}/0'/0/0`
})
}
}
})
// address
if (sourceAddress.value) {
const { prefix, data } = fromBech32(sourceAddress.value)
const chain = Object.values(dashboard.chains).find(x => x.bech32Prefix === prefix)
if (chain) {
keys.push({
cosmosAddress: sourceAddress.value,
hdPath: `m/44/${chain.coinType}/0'/0/0`
})
}
}
if (!selectedSource.value.cosmosAddress && keys.length > 0) {
selectedSource.value = keys[0]
}
return keys
})
const availableAccount = computed(() => { const availableAccount = computed(() => {
return scanCompatibleAccounts().filter(x => !addresses.value.includes(x.address)) if (selectedSource.value.cosmosAddress) {
return scanCompatibleAccounts([selectedSource.value]).filter(x => !addresses.value.includes(x.address))
}
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 => {
newConf[key] = 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
}) })
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) {
console.log('add', acc) 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
if (conf.value[key].findIndex(x => x.address === acc.address) > -1) {
return
}
conf.value[key].push(acc) conf.value[key].push(acc)
} else { } else {
conf.value[key] = [acc] conf.value[key] = [acc]
} }
if(acc.endpoint) { // also add chain to favorite
const client = CosmosRestClient.newDefault(acc.endpoint) if(!dashboard?.favoriteMap?.[acc.chainName]) {
client.getBankBalances(acc.address).then(res => { dashboard.favoriteMap[acc.chainName] = true
balances.value[acc.address] = res.balances.filter(x => x.denom.length < 10) window.localStorage.setItem(
}) 'favoriteMap',
client.getStakingDelegations(acc.address).then(res => { JSON.stringify(dashboard.favoriteMap)
delegations.value[acc.address] = res.delegation_responses );
}) }
if (acc.endpoint) {
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) {
const client = CosmosRestClient.newDefault(endpoint)
await client.getBankBalances(address).then(res => {
balances.value[address] = res.balances.filter(x => x.denom.length < 10)
})
await client.getStakingDelegations(address).then(res => {
delegations.value[address] = res.delegation_responses
})
}
</script> </script>
<template> <template>
<div> <div>
@ -169,9 +206,10 @@ async function addAddress(acc: AccountEntry) {
</span> </span>
</div> </div>
</div> </div>
<table class="table w-full">
<table class="table table-compact w-full">
<!-- head --> <!-- head -->
<thead> <thead class="rounded-none">
<tr> <tr>
<th v-if="editable"></th> <th v-if="editable"></th>
<th>Account</th> <th>Account</th>
@ -186,11 +224,11 @@ async function addAddress(acc: AccountEntry) {
<td v-if="editable"> <td v-if="editable">
<Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon> <Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon>
</td> </td>
<td> <td class="px-4">
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`"> <RouterLink :to="`/${acc.chainName}/account/${acc.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-12 h-12"> <div class="mask mask-squircle w-8 h-8">
<img :src="acc.logo" :alt="acc.address" /> <img :src="acc.logo" :alt="acc.address" />
</div> </div>
</div> </div>
@ -232,45 +270,56 @@ async function addAddress(acc: AccountEntry) {
<!-- 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">Import Accounts <h3 class="font-bold text-lg mb-2">Derive Account From Address
<div class="dropdown dropdown-hover">
<label tabindex="0" class="text-info"> </h3>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-4 h-4 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div>
<label class="input-group input-group-sm w-full">
<span>Connected</span>
<select v-model="selectedSource" class="select select-bordered select-sm w-3/4">
<option v-for="source in sourceOptions" :value="source">
<span class=" overflow-hidden">{{ source.cosmosAddress }}</span>
</option>
</select>
</label>
<label class="input-group input-group-sm my-2">
<span>Custom</span>
<input v-model="sourceAddress" class="input input-bordered w-full input-sm"
placeholder="Input an address" />
</label> </label>
<div tabindex="0" class="card compact dropdown-content dark:bg-info-content bg-slate-300 shadow rounded-box w-64">
<div class="card-body">
<p>Only shows blockchains on your favorite list</p>
</div>
</div>
</div> </div>
</h3> <div class="py-4 max-h-72 overflow-y-auto">
<p class="py-4 max-h-60 overflow-y-auto"> <table class="table table-compact">
<table> <tr v-for="acc in availableAccount">
<tr v-for="acc in availableAccount" > <td>
<td> <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="acc.logo" :alt="acc.address" />
</div> </div>
</div> </div>
<div> <div>
<div class="font-bold capitalize">{{ acc.chainName }}</div> <div class="tooltip" :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 }}
</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>
</tr> </tr>
</table> </table>
</p> </div>
<div class="modal-action"> <div class="modal-action mt-2 mb-0">
<a href="#" class="btn">Close</a> <a href="#" class="btn btn-primary btn-sm">Close</a>
</div> </div>
</div> </div>
</div> </div>
</div></template> </div></template>

View File

@ -10,39 +10,42 @@ export interface AccountEntry {
endpoint?: string, endpoint?: string,
delegation?: Coin, delegation?: Coin,
balances?: Coin[], balances?: Coin[],
compatiable?: boolean,
}
export interface LocalKey {
cosmosAddress: string, hdPath: string
} }
export function scanLocalKeys() { export function scanLocalKeys() {
const connected = [] as {cosmosAddress: string, hdPath: string}[] const connected = [] as LocalKey[]
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i) const key = localStorage.key(i)
if (key?.startsWith("m/44")) { if (key?.startsWith("m/44")) {
const wallet = JSON.parse(localStorage.getItem(key) || "") const wallet = JSON.parse(localStorage.getItem(key) || "")
if (wallet) { if (wallet) {
connected.push(wallet) connected.push(wallet)
}
} }
} }
return connected
} }
return connected
}
export function scanCompatibleAccounts(keys: LocalKey[]) {
export function scanCompatibleAccounts() { const dashboard = useDashboard()
const dashboard = useDashboard() const available = [] as AccountEntry[]
const available = [] as AccountEntry[] keys.forEach(wallet => {
scanLocalKeys().forEach(wallet => { Object.values(dashboard.chains).forEach(chain => {
Object.values(dashboard.chains).forEach(chain => { const { data } = fromBech32(wallet.cosmosAddress)
if (wallet.hdPath.indexOf(chain.coinType) === 6) { available.push({
const { data } = fromBech32(wallet.cosmosAddress) chainName: chain.chainName,
available.push({ logo: chain.logo,
chainName: chain.chainName, address: toBech32(chain.bech32Prefix, data),
logo: chain.logo, coinType: chain.coinType,
address: toBech32(chain.bech32Prefix, data), compatiable: wallet.hdPath.indexOf(chain.coinType) > 0,
coinType: chain.coinType, endpoint: chain.endpoints.rest?.at(0)?.address
endpoint: chain.endpoints.rest?.at(0)?.address
})
}
}) })
}) })
return available })
} return available
}

View File

@ -17,7 +17,7 @@ export const useWalletStore = defineStore('walletStore', {
delegations: [] as Delegation[], delegations: [] as Delegation[],
unbonding: [] as UnbondingResponses[], unbonding: [] as UnbondingResponses[],
rewards: {} as DelegatorRewards, rewards: {} as DelegatorRewards,
walletIsConnected: {} as WalletConnected | null walletIsConnected: {} as WalletConnected
}; };
}, },
getters: { getters: {