Merge pull request #8 from ping-pub/v3-single

V3 single
This commit is contained in:
Alisa | Side.one 2023-05-04 10:41:30 +08:00 committed by GitHub
commit 02a7bc5f56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 511 additions and 163 deletions

View File

@ -21,7 +21,6 @@
"@cosmjs/encoding": "^0.29.5",
"@floating-ui/dom": "^1.2.0",
"@iconify/vue": "^4.1.0",
"@injectivelabs/wallet-ts": "^1.10.69",
"@intlify/unplugin-vue-i18n": "^0.8.2",
"@osmonauts/lcd": "^0.8.0",
"@ping-pub/chain-registry-client": "^0.0.25",

View File

@ -1,85 +0,0 @@
<script lang="ts" setup>
import { Wallet } from '@injectivelabs/wallet-ts';
import { useWalletStore } from '@/stores'
import MetaLogo from '@/assets/wallet/metamask.png';
import KeplrLogo from '@/assets/wallet/keplr.png';
import LedgerLogo from '@/assets/wallet/ledger.png';
const isDialogVisible = ref(false)
interface WalletItem {
logo: string;
name: string;
wallet: Wallet;
discription: string;
}
const wallets: WalletItem[] = [
{
logo: MetaLogo,
name: 'Metamask',
wallet: Wallet.Metamask,
discription: '',
},
{
logo: KeplrLogo,
name: 'Keplr',
wallet: Wallet.Keplr,
discription: '',
},
{
logo: LedgerLogo,
name: 'Ledger',
wallet: Wallet.Ledger,
discription: '',
},
];
const store = useWalletStore();
</script>
<template>
<VDialog
v-model="isDialogVisible"
persistent
class="v-dialog-sm"
>
<!-- Dialog Activator -->
<template #activator="{ props }">
<VBtn v-bind="props">
Connect Wallet
</VBtn>
</template>
<!-- Dialog Content -->
<VCard title="Connect Wallet">
<DialogCloseBtn
variant="text"
size="small"
@click="isDialogVisible = false"
/>
<VCardText>
</VCardText>
<VCardActions>
<VSpacer />
<VBtn
color="error"
@click="isDialogVisible = false"
>
Disagree
</VBtn>
<VBtn
color="success"
@click="isDialogVisible = false"
>
Agree
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</template>

View File

@ -15,7 +15,6 @@ import NavSearchBar from './NavSearchBar.vue';
import NavBarNotifications from './NavBarNotifications.vue';
import TheCustomizer from '@/plugins/vuetify/@core/components/TheCustomizer.vue';
import Breadcrumbs from './Breadcrumbs.vue';
import ConnectWallet from '@/components/dialogs/ConnectWallet.vue'
import { useBlockchain } from '@/stores';
const {

View File

@ -1,13 +1,13 @@
import { fetchData } from '@/libs';
import { DEFAULT } from '@/libs'
import { adapter, type Request, type RequestRegistry } from './registry';
import { adapter, withCustomAdapter, type Request, type RequestRegistry, type Registry, type AbstractRegistry } from './registry';
export class CosmosRestClient {
export class BaseRestClient<R extends AbstractRegistry> {
endpoint: string;
registry: RequestRegistry;
constructor(endpoint: string, registry?: RequestRegistry) {
registry: R;
constructor(endpoint: string, registry: R) {
this.endpoint = endpoint
this.registry = registry || DEFAULT
this.registry = registry
}
async request<T>(request: Request<T>, args: Record<string, any>, query="") {
let url = `${this.endpoint}${request.url}${query}`
@ -16,6 +16,9 @@ export class CosmosRestClient {
})
return fetchData<T>(url, adapter)
}
}
export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// Auth Module
async getAuthAccounts() {
return this.request(this.registry.auth_accounts, {})

View File

@ -13,8 +13,12 @@ export interface Request<T> {
adapter: (source: any) => T
}
export interface AbstractRegistry {
[key: string]: Request<any>
}
// use snake style, since the all return object use snake style.
export interface RequestRegistry {
export interface RequestRegistry extends AbstractRegistry {
auth_params: Request<any>
auth_accounts: Request<PaginabledAccounts>;
auth_account_address: Request<{account: AuthAccount}>;
@ -102,8 +106,8 @@ export interface Registry {
[key: string]: RequestRegistry;
}
export function withCustomAdapter<T extends RequestRegistry>(target: T, source: Partial<T>): T {
return Object.assign({}, target, source);
export function withCustomAdapter<T extends RequestRegistry>(target: T, source?: Partial<T>): T {
return source ? Object.assign({}, target, source): target;
}
export function findConfigByName(name: string, registry: Registry): RequestRegistry {

View File

@ -0,0 +1,76 @@
import { BaseRestClient } from "@/libs/client";
import { adapter, type AbstractRegistry, type Request } from "@/libs/registry";
import type { PaginabledAccounts } from "@/types";
import { defineStore } from "pinia";
import type { CodeInfo, ContractInfo, PaginabledCodeInfos, PaginabledContractHistory, PaginabledContracts, PaginabledContractStates, WasmParam } from "./types";
import { toBase64 } from "@cosmjs/encoding";
import { useBlockchain } from "@/stores";
export interface WasmRequestRegistry extends AbstractRegistry {
cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{address: string, contract_info: ContractInfo}>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>;
}
export const DEFAULT: WasmRequestRegistry = {
cosmwasm_code: { url: "/cosmwasm/wasm/v1/code", adapter },
cosmwasm_code_id: { url: "/cosmwasm/wasm/v1/code/{code_id}", adapter },
cosmwasm_code_id_contracts: { url: "/cosmwasm/wasm/v1/code/{code_id}/contracts", adapter },
cosmwasm_param: { url: "/cosmwasm/wasm/v1/codes/params", adapter },
cosmwasm_contract_address: { url: "/cosmwasm/wasm/v1/contract/{address}", adapter },
cosmwasm_contract_address_history: { url: "/cosmwasm/wasm/v1/contract/{address}/history", adapter },
cosmwasm_contract_address_raw_query_data: { url: "/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}", adapter },
cosmwasm_contract_address_smart_query_data: { url: "/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}", adapter },
cosmwasm_contract_address_state: { url: "/cosmwasm/wasm/v1/contract/{address}/state", adapter }
}
class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmCodeList() {
return this.request(this.registry.cosmwasm_code, {})
}
getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, {code_id}) // `code_id` is a param in above url
}
getWasmCodeContracts(code_id: string) {
return this.request(this.registry.cosmwasm_code_id_contracts, {code_id})
}
getWasmParams() {
return this.request(this.registry.cosmwasm_param, {})
}
getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, {address})
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {address})
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query))
return this.request(this.registry.cosmwasm_contract_address_raw_query_data, {address, query_data})
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query))
return this.request(this.registry.cosmwasm_contract_address_smart_query_data, {address, query_data})
}
getWasmContractStates(address: string) {
return this.request(this.registry.cosmwasm_contract_address_state, {address})
}
}
export const useWasmStore = defineStore('module-wasm', {
state: () => {
return {}
},
getters: {
wasmClient() {
const blockchain = useBlockchain()
return new WasmRestClient(blockchain.endpoint.address, DEFAULT)
}
}
})

View File

@ -0,0 +1,151 @@
<script lang="ts" setup>
import { useBlockchain, useFormatter } from '@/stores';
import { useWasmStore } from '../WasmStore';
import { ref } from 'vue';
import type { ContractInfo, PaginabledContractStates, PaginabledContracts } from '../types';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import type CustomRadiosVue from '@/plugins/vuetify/@core/components/CustomRadios.vue';
import type { CustomInputContent } from '@/plugins/vuetify/@core/types';
const props = defineProps(['code_id', 'chain', ])
const response = ref({} as PaginabledContracts)
const wasmStore = useWasmStore()
wasmStore.wasmClient.getWasmCodeContracts(props.code_id).then(x =>{
response.value = x
})
const infoDialog = ref(false)
const stateDialog = ref(false)
const queryDialog = ref(false)
const info = ref({} as ContractInfo)
const state = ref( {} as PaginabledContractStates)
const selected = ref("")
function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then(x => {
info.value = x.contract_info
infoDialog.value = true
})
}
function showState(address: string) {
wasmStore.wasmClient.getWasmContractStates(address).then(x => {
state.value = x
stateDialog.value = true
})
}
function showQuery(address: string) {
queryDialog.value = true
selected.value = address
query.value = ""
result.value = ""
}
function queryContract() {
try{
if(selectedRadio.value === 'raw') {
wasmStore.wasmClient.getWasmContractRawQuery(selected.value, query.value).then(x => {
result.value = JSON.stringify(x)
}).catch(err => {
result.value = JSON.stringify(err)
})
} else {
wasmStore.wasmClient.getWasmContractSmartQuery(selected.value, query.value).then(x => {
result.value = JSON.stringify(x)
}).catch(err => {
result.value = JSON.stringify(err)
})
}
} catch(err) {
result.value = JSON.stringify(err) // not works for now
}
// TODO, show error in the result.
}
const radioContent: CustomInputContent[] = [
{
title: 'Raw Query',
desc: 'Return raw result',
value: 'raw',
},
{
title: 'Smart Query',
desc: 'Return structure result if possible',
value: 'smart',
},
]
const selectedRadio = ref('raw')
const query = ref("")
const result = ref("")
</script>
<template>
<div>
<VCard>
<VCardTitle>Contract List of Code: {{ props.code_id }}</VCardTitle>
<VTable>
<thead>
<tr><th>Contract List</th><th>Actions</th></tr>
</thead>
<tbody>
<tr v-for="v in response.contracts">
<td>{{ v }}</td><td>
<VBtn size="small" @click="showInfo(v)">contract</VBtn>
<VBtn size="small" @click="showState(v)" class="ml-2">States</VBtn>
<VBtn size="small" @click="showQuery(v)" class="ml-2">Query</VBtn></td>
</tr>
</tbody>
</VTable>
</VCard>
<v-dialog v-model="infoDialog" width="auto">
<v-card>
<VCardTitle>Contract Detail</VCardTitle>
<v-card-text>
<DynamicComponent :value="info"/>
</v-card-text>
<v-card-actions>
<v-btn color="primary" block @click="infoDialog = false">Close Dialog</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="stateDialog" width="auto">
<v-card>
<VCardTitle>Contract States</VCardTitle>
<VList>
<VListItem v-for="v in state.models">
<VListItemTitle>
{{ v.value }}
</VListItemTitle>
<VListItemSubtitle>
{{ v.key }}
</VListItemSubtitle>
</VListItem>
</VList>
<v-card-actions>
<v-btn color="primary" block @click="stateDialog = false">Close Dialog</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="queryDialog" width="auto">
<v-card>
<VCardTitle>Query Contract</VCardTitle>
<v-card-text>
<CustomRadios
v-model:selected-radio="selectedRadio"
:radio-content="radioContent"
:grid-column="{ sm: '6', cols: '12' }"
/>
<VTextarea v-model="query" label="Query String" class="my-2"/>
<VTextarea v-model="result" label="Result"/>
</v-card-text>
<v-card-actions>
<v-btn color="primary" @click="queryDialog = false">Close Dialog</v-btn>
<v-btn color="success" @click="queryContract()">Query Contract</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>

View File

@ -0,0 +1,43 @@
<script lang="ts" setup>
import { useBlockchain, useFormatter } from '@/stores';
import { useWasmStore } from './WasmStore';
import { ref } from 'vue';
import type { PaginabledCodeInfos } from './types';
const props = defineProps(['chain'])
const codes = ref({} as PaginabledCodeInfos)
const wasmStore = useWasmStore()
wasmStore.wasmClient.getWasmCodeList().then(x =>{
codes.value = x
})
</script>
<template>
<div>
<VCard>
<VCardTitle>Cosmos Wasm</VCardTitle>
<VTable>
<thead>
<tr><th>Code Id</th><th>Creator</th><th>Code Hash</th><th>Permissions</th></tr>
</thead>
<tbody>
<tr v-for="v in codes.code_infos">
<td>{{ v.code_id }}</td>
<td>{{ v.creator }}</td>
<td><RouterLink :to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"><div class="text-truncate" style="max-width: 200px;">{{ v.data_hash }}</div></RouterLink></td>
<td>{{ v.instantiate_permission }}</td>
</tr>
</tbody>
</VTable>
</VCard>
</div>
</template>
<route>
{
meta: {
i18n: 'cosmwasm'
}
}
</route>

View File

@ -0,0 +1,69 @@
import type { PaginatedResponse } from "@/types"
export interface CodeInfo {
code_id: string,
creator: string,
data_hash: string,
instantiate_permission: {
permission: string,
address: string,
addresses: string[]
}
}
export interface ContractInfo {
code_id: string,
creator: string,
admin: string,
label: string,
created: {
block_height: string,
tx_index: string
},
ibc_port_id: string,
extension: {
type_url: string,
value: string
}
}
export interface WasmParam {
params: {
code_upload_access: {
permission: string,
address: string,
addresses: string[]
},
instantiate_default_permission: string
}
}
export interface HistoryEntry {
operation: string,
code_id: string,
updated: {
block_height: string,
tx_index: string
},
msg: string
}
export interface Models {
key: string,
value: string
}
export interface PaginabledContractHistory extends PaginatedResponse {
entries: HistoryEntry[]
}
export interface PaginabledContractStates extends PaginatedResponse {
models: Models[]
}
export interface PaginabledCodeInfos extends PaginatedResponse {
code_infos: CodeInfo[]
}
export interface PaginabledContracts extends PaginatedResponse {
contracts: string[]
}

View File

@ -0,0 +1,86 @@
<script lang="ts" setup>
import { useBaseStore, useBlockchain, useFormatter } from '@/stores';
import type { NodeInfo } from '@/types';
import { fromBase64, toHex } from '@cosmjs/encoding';
import { onMounted, ref } from 'vue';
import { computed } from 'vue';
const props = defineProps(['hash', 'chain'])
const blockchain = useBlockchain()
const base = useBaseStore()
const nodeInfo = ref({} as NodeInfo)
const state = computed(()=> {
const rpcs = blockchain.current?.endpoints?.rpc?.map(x => x.address).join(',')
return `[statesync]
enable = true
rpc_servers = "${rpcs}"
trust_height = ${base.latest.block?.header?.height || 'loading'}
trust_hash = "${base.latest.block_id? toHex(fromBase64(base.latest.block_id?.hash)) : ''}"
trust_period = "168h" # 2/3 of unbonding time"
`
})
const appName = computed(()=> {
return nodeInfo.value.application_version?.app_name || "gaiad"
})
onMounted(() => {
blockchain.rpc.getBaseNodeInfo().then(x => {
console.log('node info', x)
nodeInfo.value = x
})
})
</script>
<template>
<div>
<VCard>
<VCardTitle>What's State Sync?</VCardTitle>
<VCardText>
The Tendermint Core 0.34 release includes support for state sync, which allows a new node to join a network by fetching a snapshot of the application state at a recent height instead of fetching and replaying all historical blocks. This can reduce the time needed to sync with the network from days to minutes. Click <a class="text-primary" href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f">here</a> for more infomation.
</VCardText>
</VCard>
<VCard class="my-5">
<VCardTitle>Starting New Node From State Sync</VCardTitle>
<VCardItem>
1. Install Binary ({{ appName }} Version: {{ nodeInfo.application_version?.version || "" }})
<br>
We need to install the binary first and make sure that the version is the one currently in use on mainnet.
<br>
2. Enable State Sync<br>
We can configure Tendermint to use state sync in $DAEMON_HOME/config/config.toml.
<VTextarea auto-grow :model-value="state"></VTextarea>
3. Start the daemon: <code>{{ appName }} start</code>
<br/>
If you are resetting node, run <code>{{ appName }} unsafe-reset-all</code> or <code>{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code> before you start the daemon.
</VCardItem>
</VCard>
<VCard>
<VCardTitle>Enable Snapshot For State Sync</VCardTitle>
<VCardItem>
To make state sync works, we can enable snapshot in $DAEMON_HOME/config/app.toml
<VTextarea auto-grow model-value="[state-sync]
# snapshot-interval specifies the block interval at which local state sync snapshots are
# taken (0 to disable). Must be a multiple of pruning-keep-every.
snapshot-interval = 1000
# snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB
snapshot-keep-recent = 2">
</VTextarea>
</VCardItem>
</VCard>
</div>
</template>
<route>
{
meta: {
i18n: 'state-sync'
}
}
</route>

View File

@ -5,7 +5,9 @@
"staking": "Staking",
"governance": "Governance",
"parameters": "Parameters",
"uptime": "Uptime"
"uptime": "Uptime",
"state-sync": "State Sync",
"cosmwasm": "Cosmwasm"
},
"index": {
"slogan": "Ping Dashboard is not just an explorer but also a wallet and more ... 🛠",

View File

@ -5,6 +5,7 @@ import { useRouter } from "vue-router";
import { CosmosRestClient } from "@/libs/client";
import { useBankStore, useBaseStore, useGovStore, useMintStore, useStakingStore } from ".";
import { useBlockModule } from "@/modules/[chain]/block/block";
import { DEFAULT } from "@/libs";
export const useBlockchain = defineStore("blockchain", {
state: () => {
@ -110,7 +111,7 @@ export const useBlockchain = defineStore("blockchain", {
async setRestEndpoint(endpoint: Endpoint) {
this.connErr = ''
this.endpoint = endpoint
this.rpc = new CosmosRestClient(endpoint.address)
this.rpc = new CosmosRestClient(endpoint.address, DEFAULT)
},
setCurrent(name: string) {
this.chainName = name

View File

@ -2,100 +2,100 @@ import type { Key } from "./common"
import type { Tx } from "./tx"
export interface NodeInfo {
"default_node_info": {
"protocol_version": {
"p2p": string,
"block": string,
"app": string
default_node_info: {
protocol_version: {
p2p: string,
block: string,
app: string
},
"default_node_id": string,
"listen_addr": string,
"network": string,
"version": string,
"channels": string,
"moniker": string,
"other": {
"tx_index": string,
"rpc_address": string
default_node_id: string,
listen_addr: string,
network: string,
version: string,
channels: string,
moniker: string,
other: {
tx_index: string,
rpc_address: string
}
},
"application_version": {
"name": string,
"app_name": string,
"version": string,
"git_commit": string,
"build_tags": string,
"go_version": string,
"build_deps": [
application_version: {
name: string,
app_name: string,
version: string,
git_commit: string,
build_tags: string,
go_version: string,
build_deps: [
{
"path": string,
"version": string,
"sum": string,
path: string,
version: string,
sum: string,
},
],
"cosmos_sdk_version": string,
cosmos_sdk_version: string,
}
}
export interface BlockId {
"hash": string,
"part_set_header": {
"total": number,
"hash": string
hash: string,
part_set_header: {
total: number,
hash: string
}
}
export interface Signature
{
"block_id_flag": string,
"validator_address": string,
"timestamp": string,
"signature": string,
block_id_flag: string,
validator_address: string,
timestamp: string,
signature: string,
}
export interface Block {
"block_id": BlockId,
"block": {
"header": {
"version": {
"block": string,
"app": string
block_id: BlockId,
block: {
header: {
version: {
block: string,
app: string
},
"chain_id": string,
"height": string,
"time": string,
"last_block_id": BlockId,
"last_commit_hash": string,
"data_hash": string,
"validators_hash": string,
"next_validators_hash": string,
"consensus_hash": string,
"app_hash": string,
"last_results_hash": string,
"evidence_hash": string,
"proposer_address": string,
chain_id: string,
height: string,
time: string,
last_block_id: BlockId,
last_commit_hash: string,
data_hash: string,
validators_hash: string,
next_validators_hash: string,
consensus_hash: string,
app_hash: string,
last_results_hash: string,
evidence_hash: string,
proposer_address: string,
},
"data": {
"txs": any[]
data: {
txs: any[]
},
"evidence": {
"evidence": any[]
evidence: {
evidence: any[]
},
"last_commit": Commit
last_commit: Commit
}
}
export interface Commit {
"height": string,
"round": number,
"block_id": BlockId,
"signatures": Signature[]
height: string,
round: number,
block_id: BlockId,
signatures: Signature[]
}
export interface TendermintValidator {
"address": string,
"pub_key": Key,
"voting_power": string,
"proposer_priority": string
address: string,
pub_key: Key,
voting_power: string,
proposer_priority: string
}
export interface PaginatedTendermintValidator {