Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# VITE_CONFIG_SOURCE - Local, ChainRegistry, MainnetCosmosDirectory, TestnetCosmosDirectory
# VITE_NETWORK_TYPE - mainnet or testnet (ignored if VITE_CHAIN_LIST is set)
# VITE_CHAIN_LIST= - Comma-separated list of chains only works for ChainRegistry right now
# VITE_COINGECKO_PRICE_API - Coingecko price API endpoint

###############################################################################
# Example: Defaults if no env vars are set
VITE_CONFIG_SOURCE="Local"
VITE_NETWORK_TYPE="mainnet"
VITE_CHAIN_LIST=""
VITE_COINGECKO_PRICE_API="https://api.coingecko.com/api/v3/simple/price"

###############################################################################
# Example: ChainRegistry all mainnet chains
VITE_CONFIG_SOURCE="ChainRegistry"

###############################################################################
# Example: ChainRegistry all testnet chains
VITE_NETWORK_TYPE="Testnet"
VITE_CONFIG_SOURCE="ChainRegistry"

###############################################################################
# Example: CosmosDirectory all mainnet chains
VITE_CONFIG_SOURCE="MainnetCosmosDirectory"

###############################################################################
# Example: CosmosDirectory all testnet chains
VITE_CONFIG_SOURCE="TestnetCosmosDirectory"

###############################################################################
# Example: ChainRegistry for specific chains
VITE_NETWORK_TYPE="mainnet" # disable Faucet
VITE_CONFIG_SOURCE="ChainRegistry"
VITE_CHAIN_LIST="xion,xiontestnet2"

###############################################################################
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ node_modules/
**/.vscode
yarn-error.log
dist
.idea
.idea

# local env files
/.env.*
/.env*.local
!/.env.example
1 change: 1 addition & 0 deletions src/components/dynamic/TextElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isBech32Address } from '@/libs/utils';
import { useBlockchain, useFormatter } from '@/stores';
import MdEditor from 'md-editor-v3';
import { computed, onMounted, ref } from 'vue';
import { default as nameMatcha } from '@leapwallet/name-matcha';

import { fromBase64, toHex } from '@cosmjs/encoding';

Expand Down
3 changes: 2 additions & 1 deletion src/layouts/components/ChainProfile.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { useBlockchain, useBaseStore, type Endpoint } from '@/stores';
import { useBlockchain, useBaseStore } from '@/stores';
import type { Endpoint } from '@/types/chaindata';
import { useRouter } from 'vue-router';
const chainStore = useBlockchain();
const baseStore = useBaseStore();
Expand Down
3 changes: 2 additions & 1 deletion src/layouts/components/DefaultLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import NavbarSearch from '@/layouts/components/NavbarSearch.vue';
import ChainProfile from '@/layouts/components/ChainProfile.vue';
import Sponsors from '@/layouts/components/Sponsors.vue';

import { NetworkType, useDashboard } from '@/stores/useDashboard';
import { useDashboard } from '@/stores/useDashboard';
import { NetworkType } from '@/types/chaindata';
import { useBaseStore, useBlockchain } from '@/stores';

import NavBarI18n from './NavBarI18n.vue';
Expand Down
88 changes: 88 additions & 0 deletions src/libs/chaindata/directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { get } from '../http';
import type { ChainConfig, Asset, Endpoint } from '@/types/chaindata';
import { ConfigSource, NetworkType } from '@/types/chaindata';

// Chain config structure of cosmos.directory
export interface DirectoryChain {
assets: Asset[];
bech32_prefix: string;
best_apis: {
rest: Endpoint[];
rpc: Endpoint[];
};
chain_id: string;
chain_name: string;
pretty_name: string;
coingecko_id: string;
cosmwasm_enabled: boolean;
decimals: number;
denom: string;
display: string;
explorers:
| {
name?: string | undefined;
kind?: string | undefined;
url?: string | undefined;
tx_page?: string | undefined;
account_page?: string | undefined;
}[]
| undefined;
height: number;
image: string;
name: string;
network_type: string;
symbol: string;
versions?: {
application_version: string;
cosmos_sdk_version: string;
tendermint_version: string;
};
}

function pathConvert(path: string | undefined) {
if (path) {
path = path.replace('https://raw.githubusercontent.com/cosmos/chain-registry/master', 'https://registry.ping.pub');
}
return path || '';
}

export async function loadFromDirectory(networkType: NetworkType): Promise<Record<string, ChainConfig>> {
const chains: Record<string, ChainConfig> = {};
const source = getConfigSource(networkType);
console.log(`Loading chains from source: ${source}`);
try {
const res = await get(source);
res.chains.forEach((x: DirectoryChain) => {
chains[x.chain_name] = convertFromDirectory(x);
});
} catch (error) {
console.error('Failed to load chains from registry:', error);
throw error;
}
return chains;
}

function getConfigSource(networkType: NetworkType): ConfigSource {
if (networkType === NetworkType.Testnet) {
return ConfigSource.TestnetCosmosDirectory;
}
return ConfigSource.MainnetCosmosDirectory;
}

function convertFromDirectory(source: DirectoryChain): ChainConfig {
const conf = {} as ChainConfig;
(conf.assets = source.assets),
(conf.bech32Prefix = source.bech32_prefix || ''),
(conf.bech32ConsensusPrefix = source.bech32_prefix + 'valcons'),
(conf.chainId = source.chain_id || ''),
(conf.chainName = source.chain_name),
(conf.prettyName = source.pretty_name || source.chain_name),
(conf.versions = {
application: source.versions?.application_version || '',
cosmosSdk: source.versions?.cosmos_sdk_version || '',
tendermint: source.versions?.tendermint_version || '',
}),
(conf.logo = pathConvert(source.image));
conf.endpoints = source.best_apis;
return conf;
}
4 changes: 4 additions & 0 deletions src/libs/chaindata/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './directory';
export * from './local';
export * from './prices';
export * from './registry';
115 changes: 115 additions & 0 deletions src/libs/chaindata/local.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import type { ChainConfig, Endpoint } from '@/types/chaindata';
import { NetworkType } from '@/types/chaindata';

export interface LocalConfig {
addr_prefix: string;
consensus_prefix?: string;
alias: string;
api: string[] | Endpoint[];
grpc: Endpoint[];
provider_chain: {
api: string[] | Endpoint[];
};
assets: {
base: string;
coingecko_id: string;
exponent: string;
logo: string;
symbol: string;
}[];
chain_name: string;
coin_type: string;
logo: string;
theme_color?: string;
min_tx_fee: string;
rpc: string[] | Endpoint[];
sdk_version: string;
registry_name?: string;
features?: string[];
keplr_price_step?: {
low: number;
average: number;
high: number;
};
keplr_features: string[];
faucet?: {
amount: string;
ip_limit: number;
address_limit: number;
fees: string;
};
}

export async function loadFromLocal(network: NetworkType) {
const config: Record<string, ChainConfig> = {};
console.log(network, NetworkType.Mainnet, NetworkType.Testnet);
const source: Record<string, LocalConfig> =
network === NetworkType.Mainnet
? import.meta.glob('../../../chains/mainnet/*.json', { eager: true })
: import.meta.glob('../../../chains/testnet/*.json', { eager: true });
Object.values<LocalConfig>(source).forEach((x: LocalConfig) => {
config[x.chain_name] = convertFromLocal(x);
});
return config;
}

export function convertFromLocal(lc: LocalConfig): ChainConfig {
const conf = {} as ChainConfig;
if (lc.assets && Array.isArray(lc.assets)) {
conf.assets = lc.assets.map((x) => ({
name: x.base,
base: x.base,
display: x.symbol,
symbol: x.symbol,
logo_URIs: { svg: x.logo },
coingecko_id: x.coingecko_id,
exponent: x.exponent,
denom_units: [
{ denom: x.base, exponent: 0 },
{ denom: x.symbol.toLowerCase(), exponent: Number(x.exponent) },
],
type_asset: 'sdk.coin',
}));
}
conf.versions = {
cosmosSdk: lc.sdk_version,
};
conf.bech32Prefix = lc.addr_prefix;
conf.bech32ConsensusPrefix = lc.consensus_prefix ?? lc.addr_prefix + 'valcons';
conf.chainName = lc.chain_name;
conf.coinType = lc.coin_type;
conf.prettyName = lc.registry_name || lc.chain_name;
conf.endpoints = {
rest: apiConverter(lc.api),
rpc: apiConverter(lc.rpc),
grpc: apiConverter(lc.grpc),
};
if (lc.provider_chain) {
conf.providerChain = {
api: apiConverter(lc.provider_chain.api),
};
}
conf.features = lc.features;
conf.logo = lc.logo.startsWith('http') ? lc.logo : `https://ping.pub${lc.logo}`;
conf.keplrFeatures = lc.keplr_features;
conf.keplrPriceStep = lc.keplr_price_step;
conf.themeColor = lc.theme_color;
conf.faucet = lc.faucet;
return conf;
}

function apiConverter(api: any[]) {
if (!api) return [];
const array = typeof api === 'string' ? [api] : api;
return array.map((x) => {
if (typeof x === 'string') {
const parts = String(x).split('.');
return {
address: x,
provider: parts.length >= 2 ? parts[parts.length - 2] : x,
};
} else {
return x as Endpoint;
}
});
}
61 changes: 61 additions & 0 deletions src/libs/chaindata/prices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { Asset, DenomUnit as AssetDenomUnit } from '@chain-registry/types';
import type { ChainConfig } from '@/types/chaindata';
import { get } from '../http';

export interface CoinGeckoPrice {
[currency: string]: number;
[currency: `${string}_24h_change`]: number;
}

export interface CoinGeckoPrices {
[coinId: string]: CoinGeckoPrice;
}

type DenomUnitData = {
coinId: string;
exponent: number;
symbol: string;
};

const coingeckoPriceApi = import.meta.env.VITE_COINGECKO_PRICE_API || 'https://api.coingecko.com/api/v3/simple/price';

export async function fetchCoinGeckoPrices(
coinIds: string[],
currencies: string[] = ['usd', 'cny']
): Promise<CoinGeckoPrices> {
try {
const prices: CoinGeckoPrices = await get(
`${coingeckoPriceApi}?include_24hr_change=true&vs_currencies=${currencies.join(',')}&ids=${Array.from(
coinIds
).join(',')}`
);
return prices;
} catch (error) {
console.error('Failed to load prices:', error);
throw error;
}
}

export async function loadPrices(chains: Record<string, ChainConfig>): Promise<CoinGeckoPrices> {
let coingecko: Record<string, DenomUnitData> = {};
const coinIds = new Set<string>();

for (const chain of Object.values(chains)) {
if (Array.isArray(chain?.assets)) {
chain.assets.forEach((asset: Asset) => {
if (asset.coingecko_id) {
coinIds.add(asset.coingecko_id);
asset.denom_units.forEach((unit: AssetDenomUnit) => {
coingecko[unit.denom] = {
coinId: asset.coingecko_id || '',
exponent: unit.exponent,
symbol: asset.symbol,
};
});
}
});
}
}

return fetchCoinGeckoPrices(Array.from(coinIds));
}
Loading