From 5896c97f3fb7fee6430b3e6153ffb56e1fdde678 Mon Sep 17 00:00:00 2001
From: tunghp2002 <atsimet@gmail.com>
Date: Thu, 19 Dec 2024 09:40:19 +0700
Subject: [PATCH 1/7] [Update] Change priority to get registry to signing

---
 .../src/koni/background/handlers/Extension.ts | 92 +++++--------------
 .../src/koni/background/utils.ts              | 69 ++++++++++++++
 .../src/services/chain-service/types.ts       |  2 +
 packages/extension-base/src/utils/metadata.ts | 31 +++++--
 4 files changed, 118 insertions(+), 76 deletions(-)
 create mode 100644 packages/extension-base/src/koni/background/utils.ts

diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts
index 7a33f1698c..833e0602e4 100644
--- a/packages/extension-base/src/koni/background/handlers/Extension.ts
+++ b/packages/extension-base/src/koni/background/handlers/Extension.ts
@@ -55,7 +55,6 @@ import { CommonOptimalPath } from '@subwallet/extension-base/types/service-base'
 import { SwapPair, SwapQuoteResponse, SwapRequest, SwapRequestResult, SwapSubmitParams, ValidateSwapProcessParams } from '@subwallet/extension-base/types/swap';
 import { _analyzeAddress, BN_ZERO, combineAllAccountProxy, createTransactionFromRLP, isSameAddress, MODULE_SUPPORT, reformatAddress, signatureToHex, Transaction as QrTransaction, transformAccounts, transformAddresses, uniqueStringArray } from '@subwallet/extension-base/utils';
 import { parseContractInput, parseEvmRlp } from '@subwallet/extension-base/utils/eth/parseTransaction';
-import { metadataExpand } from '@subwallet/extension-chains';
 import { MetadataDef } from '@subwallet/extension-inject/types';
 import { getKeypairTypeByAddress, isAddress, isSubstrateAddress, isTonAddress } from '@subwallet/keyring';
 import { EthereumKeypairTypes, SubstrateKeypairTypes, TonKeypairTypes } from '@subwallet/keyring/types';
@@ -71,12 +70,13 @@ import { combineLatest, Subject } from 'rxjs';
 import { TransactionConfig } from 'web3-core';
 
 import { SubmittableExtrinsic } from '@polkadot/api/types';
-import { Metadata, TypeRegistry } from '@polkadot/types';
-import { ChainProperties } from '@polkadot/types/interfaces';
+import { TypeRegistry } from '@polkadot/types';
 import { AnyJson, Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
 import { assert, hexStripPrefix, hexToU8a, isAscii, isHex, u8aToHex } from '@polkadot/util';
 import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto';
 
+import { getSuitableMetadata, MetadataSource, MetadataWithSource, setupDappRegistry, setupDatabaseRegistry } from '../utils';
+
 export function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is SignerPayloadJSON {
   return (value as SignerPayloadJSON).genesisHash !== undefined;
 }
@@ -2516,7 +2516,7 @@ export default class KoniExtension {
     }
   }
 
-  /// Signing substrate request
+  // Signing substrate request
   private async signingApprovePasswordV2 ({ id }: RequestSigningApprovePasswordV2): Promise<boolean> {
     const queued = this.#koniState.getSignRequest(id);
 
@@ -2527,7 +2527,7 @@ export default class KoniExtension {
 
     // unlike queued.account.address the following
     // address is encoded with the default prefix
-    // which what is used for password caching mapping
+    // which is used for password caching mapping
     const { address } = pair;
 
     if (!pair) {
@@ -2542,77 +2542,33 @@ export default class KoniExtension {
 
     const { payload } = request;
 
-    let registry: Registry;
+    let registry: Registry = new TypeRegistry();
 
     if (isJsonPayload(payload)) {
       const [, chainInfo] = this.#koniState.findNetworkKeyByGenesisHash(payload.genesisHash);
-      let metadata: MetadataDef | MetadataItem | undefined;
-
-      /**
-       *  Get the metadata for the genesisHash
-       *  @todo: need to handle case metadata store in db
-      */
-      metadata = this.#koniState.knownMetadata.find((meta: MetadataDef) =>
-        meta.genesisHash === payload.genesisHash);
+      const [apiMetadata, dbMetadata, dappMetadata] = await Promise.all([
+        { metadata: this.#koniState.getSubstrateApi(chainInfo?.slug ?? '').metadata, source: MetadataSource.API },
+        { metadata: await this.#koniState.chainService.getMetadataByHash(payload.genesisHash), source: MetadataSource.DB },
+        { metadata: this.#koniState.knownMetadata.find((meta: MetadataDef) => meta.genesisHash === payload.genesisHash), source: MetadataSource.DAPP }
+      ]);
 
-      if (metadata) {
-        // we have metadata, expand it and extract the info/registry
-        const expanded = metadataExpand(metadata, false);
+      const allMetadata: MetadataWithSource[] = [
+        { metadata: apiMetadata.metadata, source: apiMetadata.source },
+        { metadata: dbMetadata.metadata, source: dbMetadata.source },
+        { metadata: dappMetadata.metadata, source: dappMetadata.source }
+      ].filter((item) => item.metadata);
 
-        registry = expanded.registry;
-        registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);
+      if (allMetadata.length === 0) {
+        registry.setSignedExtensions(payload.signedExtensions);
       } else {
-        metadata = await this.#koniState.chainService.getMetadataByHash(payload.genesisHash);
-
-        if (metadata) {
-          registry = new TypeRegistry();
-
-          const _metadata = new Metadata(registry, metadata.hexValue);
+        const { metadata, source } = getSuitableMetadata(allMetadata, parseInt(payload.specVersion));
 
-          registry.register(metadata.types);
-          registry.setChainProperties(registry.createType('ChainProperties', {
-            ss58Format: chainInfo?.substrateInfo?.addressPrefix ?? 42,
-            tokenDecimals: chainInfo?.substrateInfo?.decimals,
-            tokenSymbol: chainInfo?.substrateInfo?.symbol
-          }) as unknown as ChainProperties);
-          registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);
-        } else {
-          // we have no metadata, create a new registry
-          registry = new TypeRegistry();
-          registry.setSignedExtensions(payload.signedExtensions);
-        }
+        registry = source === MetadataSource.API && chainInfo
+          ? this.#koniState.getSubstrateApi(chainInfo.slug).api.registry as unknown as TypeRegistry
+          : source === MetadataSource.DB && chainInfo
+            ? setupDatabaseRegistry(metadata as MetadataItem, chainInfo, payload)
+            : setupDappRegistry(metadata as MetadataDef, payload);
       }
-
-      if (!metadata) {
-        /*
-        * Some networks must have metadata to signing,
-        * so if the chain not active (cannot use metadata from api), it must be rejected
-        *  */
-        if (
-          chainInfo &&
-          (_API_OPTIONS_CHAIN_GROUP.avail.includes(chainInfo.slug) || _API_OPTIONS_CHAIN_GROUP.goldberg.includes(chainInfo.slug)) // The special case for chains that need metadata to signing
-        ) {
-          // For case the chain does not have any provider
-          if (!Object.keys(chainInfo.providers).length) {
-            reject(new Error('{{chain}} network does not have any provider to connect, please update metadata from dApp'.replaceAll('{{chain}}', chainInfo.name)));
-
-            return false;
-          }
-
-          const isChainActive = this.#koniState.getChainStateByKey(chainInfo.slug).active;
-
-          if (!isChainActive) {
-            reject(new Error('Please activate {{chain}} network before signing'.replaceAll('{{chain}}', chainInfo.name)));
-
-            return false;
-          }
-
-          registry = this.#koniState.getSubstrateApi(chainInfo.slug).api.registry as unknown as TypeRegistry;
-        }
-      }
-    } else {
-      // for non-payload, just create a registry to use
-      registry = new TypeRegistry();
     }
 
     const result = request.sign(registry as unknown as TypeRegistry, pair);
diff --git a/packages/extension-base/src/koni/background/utils.ts b/packages/extension-base/src/koni/background/utils.ts
new file mode 100644
index 0000000000..0df2d98858
--- /dev/null
+++ b/packages/extension-base/src/koni/background/utils.ts
@@ -0,0 +1,69 @@
+// Copyright 2019-2022 @subwallet/extension-koni authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import { _ChainInfo } from '@subwallet/chain-list/types';
+import { MetadataItem } from '@subwallet/extension-base/background/KoniTypes';
+import { metadataExpand } from '@subwallet/extension-chains/bundle';
+import { MetadataDef } from '@subwallet/extension-inject/types';
+
+import { Metadata, TypeRegistry } from '@polkadot/types';
+import { ChainProperties } from '@polkadot/types/interfaces';
+import { SignerPayloadJSON } from '@polkadot/types/types';
+
+export enum MetadataSource {
+  API = 'api',
+  DB = 'db',
+  DAPP = 'dapp'
+}
+
+export interface MetadataWithSource{
+  metadata: MetadataDef | MetadataItem | undefined;
+  source: MetadataSource;
+}
+
+export function getSuitableMetadata (metadatas: MetadataWithSource[], payloadSpecVersion: number) {
+  const sortedMetadatas = metadatas
+    .filter((meta): meta is MetadataWithSource => meta.metadata !== undefined)
+    .map((meta) => ({
+      meta: meta.metadata,
+      source: meta.source,
+      distance: Math.abs(Number(meta.metadata?.specVersion) - payloadSpecVersion),
+      isHigher: Number(meta.metadata?.specVersion) >= payloadSpecVersion
+    }))
+    .sort((a, b) => {
+      if (a.distance !== b.distance) {
+        return a.distance - b.distance;
+      }
+
+      return Number(b.meta?.specVersion) - Number(a.meta?.specVersion);
+    });
+
+  return {
+    metadata: sortedMetadatas[0].meta,
+    source: sortedMetadatas[0].source
+  };
+}
+
+export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _ChainInfo, payload: SignerPayloadJSON) {
+  const registry = new TypeRegistry();
+  const _metadata = new Metadata(registry, metadata.hexValue);
+
+  registry.register(metadata.types);
+  registry.setChainProperties(registry.createType('ChainProperties', {
+    ss58Format: chainInfo?.substrateInfo?.addressPrefix ?? 42,
+    tokenDecimals: chainInfo?.substrateInfo?.decimals,
+    tokenSymbol: chainInfo?.substrateInfo?.symbol
+  }) as unknown as ChainProperties);
+  registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);
+
+  return registry;
+}
+
+export function setupDappRegistry (metadata: MetadataDef, payload: SignerPayloadJSON) {
+  const expanded = metadataExpand(metadata, false);
+  const registry = expanded.registry;
+
+  registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);
+
+  return registry;
+}
diff --git a/packages/extension-base/src/services/chain-service/types.ts b/packages/extension-base/src/services/chain-service/types.ts
index 5d0b84b84d..6a975af9c6 100644
--- a/packages/extension-base/src/services/chain-service/types.ts
+++ b/packages/extension-base/src/services/chain-service/types.ts
@@ -6,6 +6,7 @@
 import type { ApiInterfaceRx } from '@polkadot/api/types';
 
 import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _CrowdloanFund } from '@subwallet/chain-list/types';
+import { MetadataItem } from '@subwallet/extension-base/background/KoniTypes';
 import { AccountState, TxByMsgResponse } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/types';
 import { _CHAIN_VALIDATION_ERROR } from '@subwallet/extension-base/services/chain-service/handler/types';
 import { TonWalletContract } from '@subwallet/keyring/types';
@@ -94,6 +95,7 @@ export interface _SubstrateApiState {
 }
 
 export interface _SubstrateApi extends _SubstrateApiState, _ChainBaseApi, _SubstrateApiAdapter {
+  metadata?: MetadataItem;
   api: ApiPromise;
   isReady: Promise<_SubstrateApi>;
   connect: (_callbackUpdateMetadata?: (substrateApi: _SubstrateApi) => void) => void;
diff --git a/packages/extension-base/src/utils/metadata.ts b/packages/extension-base/src/utils/metadata.ts
index 41c6c47c2d..6dfd1eadf9 100644
--- a/packages/extension-base/src/utils/metadata.ts
+++ b/packages/extension-base/src/utils/metadata.ts
@@ -4,6 +4,7 @@
 import { ChainService } from '@subwallet/extension-base/services/chain-service';
 import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types';
 
+import { ApiPromise } from '@polkadot/api';
 import { getSpecExtensions, getSpecTypes } from '@polkadot/types-known';
 import { u8aToHex } from '@polkadot/util';
 import { HexString } from '@polkadot/util/types';
@@ -25,6 +26,22 @@ export const getShortMetadata = (blob: HexString, extraInfo: ExtraInfo, metadata
   return u8aToHex(_merkleizeMetadata.getProofForExtrinsicPayload(blob));
 };
 
+const getMetadataV15 = async (api: ApiPromise): Promise<HexString | undefined> => {
+  try {
+    if (api.call.metadata.metadataAtVersion) {
+      const metadataV15 = await api.call.metadata.metadataAtVersion(15);
+
+      if (!metadataV15.isEmpty) {
+        return metadataV15.unwrap().toHex();
+      }
+    }
+  } catch (err) {
+    console.error('Error fetching metadata V15:', err);
+  }
+
+  return undefined;
+};
+
 export const cacheMetadata = (
   chain: string,
   substrateApi: _SubstrateApi,
@@ -42,23 +59,21 @@ export const cacheMetadata = (
       return;
     }
 
-    const systemChain = await api.rpc.system.chain();
+    const systemChain = api.runtimeChain;
     // const _metadata: Option<OpaqueMetadata> = await api.call.metadata.metadataAtVersion(15);
     // const metadataHex = _metadata.isSome ? _metadata.unwrap().toHex().slice(2) : ''; // Need unwrap to create metadata object
-    let hexV15: HexString | undefined;
-
-    const metadataV15 = await api.call.metadata.metadataAtVersion(15);
 
-    if (!metadataV15.isEmpty) {
-      hexV15 = metadataV15.unwrap().toHex();
-    }
+    const [metadataHex, hexV15] = await Promise.all([
+      Promise.resolve(api.runtimeMetadata.toHex()),
+      getMetadataV15(api)
+    ]);
 
     chainService?.upsertMetadata(chain, {
       chain: chain,
       genesisHash: genesisHash,
       specName: specName,
       specVersion: currentSpecVersion,
-      hexValue: api.runtimeMetadata.toHex(),
+      hexValue: metadataHex,
       types: getSpecTypes(api.registry, systemChain, api.runtimeVersion.specName, api.runtimeVersion.specVersion) as unknown as Record<string, string>,
       userExtensions: getSpecExtensions(api.registry, systemChain, api.runtimeVersion.specName),
       hexV15

From ccb80e45593730f88a44e33af38b2b14866182a6 Mon Sep 17 00:00:00 2001
From: tunghp2002 <atsimet@gmail.com>
Date: Mon, 23 Dec 2024 11:11:32 +0700
Subject: [PATCH 2/7] [Update] Refactor priority logic

---
 .../src/koni/background/handlers/Extension.ts | 14 +---
 .../src/koni/background/utils.ts              | 73 ++++++++++++++++---
 packages/extension-base/src/utils/metadata.ts | 43 ++++++-----
 3 files changed, 88 insertions(+), 42 deletions(-)

diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts
index 833e0602e4..39d47ee680 100644
--- a/packages/extension-base/src/koni/background/handlers/Extension.ts
+++ b/packages/extension-base/src/koni/background/handlers/Extension.ts
@@ -75,7 +75,7 @@ import { AnyJson, Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkado
 import { assert, hexStripPrefix, hexToU8a, isAscii, isHex, u8aToHex } from '@polkadot/util';
 import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto';
 
-import { getSuitableMetadata, MetadataSource, MetadataWithSource, setupDappRegistry, setupDatabaseRegistry } from '../utils';
+import { getSuitableRegistry, MetadataSource, MetadataWithSource } from '../utils';
 
 export function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is SignerPayloadJSON {
   return (value as SignerPayloadJSON).genesisHash !== undefined;
@@ -2546,14 +2546,12 @@ export default class KoniExtension {
 
     if (isJsonPayload(payload)) {
       const [, chainInfo] = this.#koniState.findNetworkKeyByGenesisHash(payload.genesisHash);
-      const [apiMetadata, dbMetadata, dappMetadata] = await Promise.all([
-        { metadata: this.#koniState.getSubstrateApi(chainInfo?.slug ?? '').metadata, source: MetadataSource.API },
+      const [dbMetadata, dappMetadata] = await Promise.all([
         { metadata: await this.#koniState.chainService.getMetadataByHash(payload.genesisHash), source: MetadataSource.DB },
         { metadata: this.#koniState.knownMetadata.find((meta: MetadataDef) => meta.genesisHash === payload.genesisHash), source: MetadataSource.DAPP }
       ]);
 
       const allMetadata: MetadataWithSource[] = [
-        { metadata: apiMetadata.metadata, source: apiMetadata.source },
         { metadata: dbMetadata.metadata, source: dbMetadata.source },
         { metadata: dappMetadata.metadata, source: dappMetadata.source }
       ].filter((item) => item.metadata);
@@ -2561,13 +2559,7 @@ export default class KoniExtension {
       if (allMetadata.length === 0) {
         registry.setSignedExtensions(payload.signedExtensions);
       } else {
-        const { metadata, source } = getSuitableMetadata(allMetadata, parseInt(payload.specVersion));
-
-        registry = source === MetadataSource.API && chainInfo
-          ? this.#koniState.getSubstrateApi(chainInfo.slug).api.registry as unknown as TypeRegistry
-          : source === MetadataSource.DB && chainInfo
-            ? setupDatabaseRegistry(metadata as MetadataItem, chainInfo, payload)
-            : setupDappRegistry(metadata as MetadataDef, payload);
+        registry = getSuitableRegistry(allMetadata, payload, chainInfo, this.#koniState);
       }
     }
 
diff --git a/packages/extension-base/src/koni/background/utils.ts b/packages/extension-base/src/koni/background/utils.ts
index 0df2d98858..bd5f447763 100644
--- a/packages/extension-base/src/koni/background/utils.ts
+++ b/packages/extension-base/src/koni/background/utils.ts
@@ -8,7 +8,9 @@ import { MetadataDef } from '@subwallet/extension-inject/types';
 
 import { Metadata, TypeRegistry } from '@polkadot/types';
 import { ChainProperties } from '@polkadot/types/interfaces';
-import { SignerPayloadJSON } from '@polkadot/types/types';
+import { Registry, SignerPayloadJSON } from '@polkadot/types/types';
+
+import KoniState from './handlers/State';
 
 export enum MetadataSource {
   API = 'api',
@@ -21,8 +23,49 @@ export interface MetadataWithSource{
   source: MetadataSource;
 }
 
-export function getSuitableMetadata (metadatas: MetadataWithSource[], payloadSpecVersion: number) {
-  const sortedMetadatas = metadatas
+export interface CachedChainProperties {
+  ss58Format: number;
+  tokenDecimals: number | undefined;
+  tokenSymbol: string | undefined;
+}
+
+const cachedChainProperties: Map<string, Promise<CachedChainProperties | null>> = new Map();
+
+function getChainProperties (chainInfo: _ChainInfo, genesisHash: string): Promise<CachedChainProperties | null> {
+  const cachedPromise = cachedChainProperties.get(genesisHash);
+
+  if (cachedPromise) {
+    return cachedPromise;
+  }
+
+  const chainPropertiesPromise = new Promise<CachedChainProperties | null>((resolve) => {
+    const chainProperties: CachedChainProperties = {
+      ss58Format: chainInfo?.substrateInfo?.addressPrefix ?? 42,
+      tokenDecimals: chainInfo?.substrateInfo?.decimals,
+      tokenSymbol: chainInfo?.substrateInfo?.symbol
+    };
+
+    cachedChainProperties.set(genesisHash, Promise.resolve(chainProperties));
+    resolve(chainProperties);
+  });
+
+  cachedChainProperties.set(genesisHash, chainPropertiesPromise);
+
+  return chainPropertiesPromise;
+}
+
+export function getSuitableRegistry (metadatas: MetadataWithSource[], payload: SignerPayloadJSON, chainInfo: _ChainInfo | undefined, koniState: KoniState) {
+  const api = chainInfo ? koniState.getSubstrateApi(chainInfo.slug).api : undefined;
+  const apiSpecVersion = api?.runtimeVersion.specVersion.toString();
+  const payloadSpecVersion = parseInt(payload.specVersion);
+  const extendedMetadatas = [{
+    metadata: { specVersion: apiSpecVersion },
+    source: MetadataSource.API
+  },
+  ...metadatas
+  ];
+
+  const sortedMetadatas = extendedMetadatas
     .filter((meta): meta is MetadataWithSource => meta.metadata !== undefined)
     .map((meta) => ({
       meta: meta.metadata,
@@ -38,22 +81,28 @@ export function getSuitableMetadata (metadatas: MetadataWithSource[], payloadSpe
       return Number(b.meta?.specVersion) - Number(a.meta?.specVersion);
     });
 
-  return {
-    metadata: sortedMetadatas[0].meta,
-    source: sortedMetadatas[0].source
-  };
+  const closestMetadata = sortedMetadatas[0];
+  let registry: Registry;
+
+  if (closestMetadata.source === MetadataSource.API) {
+    registry = api?.registry as unknown as TypeRegistry;
+  } else if (closestMetadata.source === MetadataSource.DB && chainInfo) {
+    registry = setupDatabaseRegistry(closestMetadata.meta as MetadataItem, chainInfo, payload);
+  } else {
+    registry = setupDappRegistry(closestMetadata.meta as MetadataDef, payload);
+  }
+
+  return registry;
 }
 
 export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _ChainInfo, payload: SignerPayloadJSON) {
   const registry = new TypeRegistry();
   const _metadata = new Metadata(registry, metadata.hexValue);
 
+  const chainProperties = getChainProperties(chainInfo, payload.genesisHash);
+
   registry.register(metadata.types);
-  registry.setChainProperties(registry.createType('ChainProperties', {
-    ss58Format: chainInfo?.substrateInfo?.addressPrefix ?? 42,
-    tokenDecimals: chainInfo?.substrateInfo?.decimals,
-    tokenSymbol: chainInfo?.substrateInfo?.symbol
-  }) as unknown as ChainProperties);
+  registry.setChainProperties(registry.createType('ChainProperties', chainProperties) as unknown as ChainProperties);
   registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);
 
   return registry;
diff --git a/packages/extension-base/src/utils/metadata.ts b/packages/extension-base/src/utils/metadata.ts
index 6dfd1eadf9..e0df7b96d8 100644
--- a/packages/extension-base/src/utils/metadata.ts
+++ b/packages/extension-base/src/utils/metadata.ts
@@ -26,20 +26,27 @@ export const getShortMetadata = (blob: HexString, extraInfo: ExtraInfo, metadata
   return u8aToHex(_merkleizeMetadata.getProofForExtrinsicPayload(blob));
 };
 
-const getMetadataV15 = async (api: ApiPromise): Promise<HexString | undefined> => {
-  try {
-    if (api.call.metadata.metadataAtVersion) {
-      const metadataV15 = await api.call.metadata.metadataAtVersion(15);
+const storeMetadataV15: Map<string, HexString | undefined> = new Map();
 
+const getMetadataV15 = (api: ApiPromise) => {
+  const genesisHash = api.genesisHash.toHex();
+
+  api.call.metadata.metadataAtVersion(15)
+    .then((metadataV15) => {
       if (!metadataV15.isEmpty) {
-        return metadataV15.unwrap().toHex();
+        const hexValue = metadataV15.unwrap().toHex();
+
+        storeMetadataV15.set(genesisHash, hexValue);
+      } else {
+        storeMetadataV15.set(genesisHash, undefined);
       }
-    }
-  } catch (err) {
-    console.error('Error fetching metadata V15:', err);
-  }
+    })
+    .catch((err) => {
+      console.error('Error:', err);
+      storeMetadataV15.set(genesisHash, undefined);
+    });
 
-  return undefined;
+  return storeMetadataV15.get(genesisHash);
 };
 
 export const cacheMetadata = (
@@ -62,21 +69,19 @@ export const cacheMetadata = (
     const systemChain = api.runtimeChain;
     // const _metadata: Option<OpaqueMetadata> = await api.call.metadata.metadataAtVersion(15);
     // const metadataHex = _metadata.isSome ? _metadata.unwrap().toHex().slice(2) : ''; // Need unwrap to create metadata object
+    const metadataHex = api.runtimeMetadata.toHex();
+    const metadataV15 = getMetadataV15(api);
 
-    const [metadataHex, hexV15] = await Promise.all([
-      Promise.resolve(api.runtimeMetadata.toHex()),
-      getMetadataV15(api)
-    ]);
-
-    chainService?.upsertMetadata(chain, {
+    const updateMetadata = {
       chain: chain,
       genesisHash: genesisHash,
       specName: specName,
       specVersion: currentSpecVersion,
       hexValue: metadataHex,
       types: getSpecTypes(api.registry, systemChain, api.runtimeVersion.specName, api.runtimeVersion.specVersion) as unknown as Record<string, string>,
-      userExtensions: getSpecExtensions(api.registry, systemChain, api.runtimeVersion.specName),
-      hexV15
-    }).catch(console.error);
+      userExtensions: getSpecExtensions(api.registry, systemChain, api.runtimeVersion.specName)
+    };
+
+    chainService?.upsertMetadata(chain, { ...updateMetadata, hexV15: metadataV15 }).catch(console.error);
   }).catch(console.error);
 };

From 5cbb5b9d2d69799c74f57fbee154ef571af8cfbe Mon Sep 17 00:00:00 2001
From: tunghp2002 <atsimet@gmail.com>
Date: Mon, 23 Dec 2024 16:16:22 +0700
Subject: [PATCH 3/7] [Update] Refactor priority logic v2

---
 .../src/background/KoniTypes.ts               |   3 +
 .../src/koni/background/handlers/Extension.ts |  28 ++--
 .../src/koni/background/utils.ts              | 132 ++++++++----------
 .../src/services/chain-service/types.ts       |   2 -
 packages/extension-base/src/utils/metadata.ts |  74 +++++-----
 5 files changed, 117 insertions(+), 122 deletions(-)

diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts
index ff96b76aeb..b297bc6c8a 100644
--- a/packages/extension-base/src/background/KoniTypes.ts
+++ b/packages/extension-base/src/background/KoniTypes.ts
@@ -277,6 +277,9 @@ export interface MetadataItem {
   types: Record<string, Record<string, string> | string>;
   userExtensions?: ExtDef;
   hexV15?: HexString;
+  ss58Format?: number;
+  tokenDecimals?: number;
+  tokenSymbol?: string;
 }
 
 export interface CrowdloanItem {
diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts
index 39d47ee680..00a9267a25 100644
--- a/packages/extension-base/src/koni/background/handlers/Extension.ts
+++ b/packages/extension-base/src/koni/background/handlers/Extension.ts
@@ -75,7 +75,7 @@ import { AnyJson, Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkado
 import { assert, hexStripPrefix, hexToU8a, isAscii, isHex, u8aToHex } from '@polkadot/util';
 import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto';
 
-import { getSuitableRegistry, MetadataSource, MetadataWithSource } from '../utils';
+import { getSuitableRegistry, RegistrySource, setupApiRegistry, setupDappRegistry, setupDatabaseRegistry } from '../utils';
 
 export function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is SignerPayloadJSON {
   return (value as SignerPayloadJSON).genesisHash !== undefined;
@@ -2546,20 +2546,24 @@ export default class KoniExtension {
 
     if (isJsonPayload(payload)) {
       const [, chainInfo] = this.#koniState.findNetworkKeyByGenesisHash(payload.genesisHash);
-      const [dbMetadata, dappMetadata] = await Promise.all([
-        { metadata: await this.#koniState.chainService.getMetadataByHash(payload.genesisHash), source: MetadataSource.DB },
-        { metadata: this.#koniState.knownMetadata.find((meta: MetadataDef) => meta.genesisHash === payload.genesisHash), source: MetadataSource.DAPP }
-      ]);
-
-      const allMetadata: MetadataWithSource[] = [
-        { metadata: dbMetadata.metadata, source: dbMetadata.source },
-        { metadata: dappMetadata.metadata, source: dappMetadata.source }
-      ].filter((item) => item.metadata);
 
-      if (allMetadata.length === 0) {
+      const allRegistry: RegistrySource[] = [
+        setupApiRegistry(chainInfo, this.#koniState),
+        setupDatabaseRegistry(
+          await this.#koniState.chainService.getMetadataByHash(payload.genesisHash) as MetadataItem,
+          chainInfo,
+          payload
+        ),
+        setupDappRegistry(
+          this.#koniState.knownMetadata.find((meta: MetadataDef) => meta.genesisHash === payload.genesisHash) as MetadataDef,
+          payload
+        )
+      ].filter((item): item is RegistrySource => item !== null && item.registry !== undefined);
+
+      if (allRegistry.length === 0) {
         registry.setSignedExtensions(payload.signedExtensions);
       } else {
-        registry = getSuitableRegistry(allMetadata, payload, chainInfo, this.#koniState);
+        registry = getSuitableRegistry(allRegistry, payload).metadata;
       }
     }
 
diff --git a/packages/extension-base/src/koni/background/utils.ts b/packages/extension-base/src/koni/background/utils.ts
index bd5f447763..e4ca8a42ba 100644
--- a/packages/extension-base/src/koni/background/utils.ts
+++ b/packages/extension-base/src/koni/background/utils.ts
@@ -12,107 +12,89 @@ import { Registry, SignerPayloadJSON } from '@polkadot/types/types';
 
 import KoniState from './handlers/State';
 
-export enum MetadataSource {
-  API = 'api',
-  DB = 'db',
-  DAPP = 'dapp'
+export interface RegistrySource{
+  registry: Registry,
+  specVersion: string | number,
 }
 
-export interface MetadataWithSource{
-  metadata: MetadataDef | MetadataItem | undefined;
-  source: MetadataSource;
-}
-
-export interface CachedChainProperties {
-  ss58Format: number;
-  tokenDecimals: number | undefined;
-  tokenSymbol: string | undefined;
-}
-
-const cachedChainProperties: Map<string, Promise<CachedChainProperties | null>> = new Map();
-
-function getChainProperties (chainInfo: _ChainInfo, genesisHash: string): Promise<CachedChainProperties | null> {
-  const cachedPromise = cachedChainProperties.get(genesisHash);
-
-  if (cachedPromise) {
-    return cachedPromise;
-  }
-
-  const chainPropertiesPromise = new Promise<CachedChainProperties | null>((resolve) => {
-    const chainProperties: CachedChainProperties = {
-      ss58Format: chainInfo?.substrateInfo?.addressPrefix ?? 42,
-      tokenDecimals: chainInfo?.substrateInfo?.decimals,
-      tokenSymbol: chainInfo?.substrateInfo?.symbol
-    };
-
-    cachedChainProperties.set(genesisHash, Promise.resolve(chainProperties));
-    resolve(chainProperties);
-  });
-
-  cachedChainProperties.set(genesisHash, chainPropertiesPromise);
-
-  return chainPropertiesPromise;
-}
-
-export function getSuitableRegistry (metadatas: MetadataWithSource[], payload: SignerPayloadJSON, chainInfo: _ChainInfo | undefined, koniState: KoniState) {
-  const api = chainInfo ? koniState.getSubstrateApi(chainInfo.slug).api : undefined;
-  const apiSpecVersion = api?.runtimeVersion.specVersion.toString();
+export function getSuitableRegistry (registries: RegistrySource[], payload: SignerPayloadJSON) {
   const payloadSpecVersion = parseInt(payload.specVersion);
-  const extendedMetadatas = [{
-    metadata: { specVersion: apiSpecVersion },
-    source: MetadataSource.API
-  },
-  ...metadatas
-  ];
-
-  const sortedMetadatas = extendedMetadatas
-    .filter((meta): meta is MetadataWithSource => meta.metadata !== undefined)
-    .map((meta) => ({
-      meta: meta.metadata,
-      source: meta.source,
-      distance: Math.abs(Number(meta.metadata?.specVersion) - payloadSpecVersion),
-      isHigher: Number(meta.metadata?.specVersion) >= payloadSpecVersion
-    }))
+  const sortedRegistries = registries
+    .filter((registrySource): registrySource is RegistrySource => registrySource.registry !== undefined)
+    .map((registrySource) => {
+      const specVersion = Number(registrySource.specVersion);
+      const distance = Math.abs(specVersion - payloadSpecVersion);
+      const isHigher = specVersion >= payloadSpecVersion;
+
+      return {
+        registry: registrySource.registry,
+        specVersion,
+        distance,
+        isHigher
+      };
+    })
     .sort((a, b) => {
       if (a.distance !== b.distance) {
         return a.distance - b.distance;
       }
 
-      return Number(b.meta?.specVersion) - Number(a.meta?.specVersion);
+      return a.specVersion - b.specVersion;
     });
 
-  const closestMetadata = sortedMetadatas[0];
-  let registry: Registry;
+  return {
+    metadata: sortedRegistries[0].registry
+  };
+}
 
-  if (closestMetadata.source === MetadataSource.API) {
-    registry = api?.registry as unknown as TypeRegistry;
-  } else if (closestMetadata.source === MetadataSource.DB && chainInfo) {
-    registry = setupDatabaseRegistry(closestMetadata.meta as MetadataItem, chainInfo, payload);
-  } else {
-    registry = setupDappRegistry(closestMetadata.meta as MetadataDef, payload);
+export function setupApiRegistry (chainInfo: _ChainInfo | undefined, koniState: KoniState): RegistrySource | null {
+  if (!chainInfo) {
+    return null;
   }
 
-  return registry;
+  const api = koniState.getSubstrateApi(chainInfo.slug).api;
+  const apiSpecVersion = api?.runtimeVersion.specVersion.toString();
+  const registry = api?.registry as unknown as TypeRegistry;
+
+  return {
+    registry,
+    specVersion: apiSpecVersion
+  };
 }
 
-export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _ChainInfo, payload: SignerPayloadJSON) {
+export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _ChainInfo | undefined, payload: SignerPayloadJSON): RegistrySource | null {
+  if (!metadata || !metadata.genesisHash || !chainInfo) {
+    return null;
+  }
+
   const registry = new TypeRegistry();
   const _metadata = new Metadata(registry, metadata.hexValue);
 
-  const chainProperties = getChainProperties(chainInfo, payload.genesisHash);
-
   registry.register(metadata.types);
-  registry.setChainProperties(registry.createType('ChainProperties', chainProperties) as unknown as ChainProperties);
+  registry.setChainProperties(registry.createType('ChainProperties', {
+    ss58Format: metadata.ss58Format,
+    tokenDecimals: metadata.tokenDecimals,
+    tokenSymbol: metadata.tokenSymbol
+  }) as unknown as ChainProperties);
   registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);
 
-  return registry;
+  return {
+    registry,
+    specVersion: metadata.specVersion
+  };
 }
 
-export function setupDappRegistry (metadata: MetadataDef, payload: SignerPayloadJSON) {
+export function setupDappRegistry (metadata: MetadataDef, payload: SignerPayloadJSON): RegistrySource | null {
+  if (!metadata || !metadata.genesisHash) {
+    return null;
+  }
+
   const expanded = metadataExpand(metadata, false);
   const registry = expanded.registry;
 
   registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);
 
-  return registry;
+  return {
+    registry,
+    specVersion: metadata.specVersion
+  };
 }
diff --git a/packages/extension-base/src/services/chain-service/types.ts b/packages/extension-base/src/services/chain-service/types.ts
index 6a975af9c6..5d0b84b84d 100644
--- a/packages/extension-base/src/services/chain-service/types.ts
+++ b/packages/extension-base/src/services/chain-service/types.ts
@@ -6,7 +6,6 @@
 import type { ApiInterfaceRx } from '@polkadot/api/types';
 
 import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _CrowdloanFund } from '@subwallet/chain-list/types';
-import { MetadataItem } from '@subwallet/extension-base/background/KoniTypes';
 import { AccountState, TxByMsgResponse } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/types';
 import { _CHAIN_VALIDATION_ERROR } from '@subwallet/extension-base/services/chain-service/handler/types';
 import { TonWalletContract } from '@subwallet/keyring/types';
@@ -95,7 +94,6 @@ export interface _SubstrateApiState {
 }
 
 export interface _SubstrateApi extends _SubstrateApiState, _ChainBaseApi, _SubstrateApiAdapter {
-  metadata?: MetadataItem;
   api: ApiPromise;
   isReady: Promise<_SubstrateApi>;
   connect: (_callbackUpdateMetadata?: (substrateApi: _SubstrateApi) => void) => void;
diff --git a/packages/extension-base/src/utils/metadata.ts b/packages/extension-base/src/utils/metadata.ts
index e0df7b96d8..5e5db113bd 100644
--- a/packages/extension-base/src/utils/metadata.ts
+++ b/packages/extension-base/src/utils/metadata.ts
@@ -26,27 +26,51 @@ export const getShortMetadata = (blob: HexString, extraInfo: ExtraInfo, metadata
   return u8aToHex(_merkleizeMetadata.getProofForExtrinsicPayload(blob));
 };
 
-const storeMetadataV15: Map<string, HexString | undefined> = new Map();
+const getMetadataV15 = async (chain: string, api: ApiPromise, chainService?: ChainService): Promise<void> => {
+  try {
+    if (api.call.metadata.metadataAtVersion) {
+      const metadataV15 = await api.call.metadata.metadataAtVersion(15);
 
-const getMetadataV15 = (api: ApiPromise) => {
-  const genesisHash = api.genesisHash.toHex();
-
-  api.call.metadata.metadataAtVersion(15)
-    .then((metadataV15) => {
       if (!metadataV15.isEmpty) {
         const hexValue = metadataV15.unwrap().toHex();
 
-        storeMetadataV15.set(genesisHash, hexValue);
-      } else {
-        storeMetadataV15.set(genesisHash, undefined);
+        if (chainService) {
+          const metadata = await chainService.getMetadata(chain);
+
+          if (metadata) {
+            await chainService.upsertMetadata(chain, { ...metadata, hexV15: hexValue });
+          }
+        }
       }
-    })
-    .catch((err) => {
-      console.error('Error:', err);
-      storeMetadataV15.set(genesisHash, undefined);
-    });
+    }
+  } catch (err) {
+    console.error('Error:', err);
+  }
+};
 
-  return storeMetadataV15.get(genesisHash);
+const getMetadata = (chain: string, api: ApiPromise, currentSpecVersion: string, genesisHash: HexString, chainService?: ChainService) => {
+  const specName = api.runtimeVersion.specName.toString();
+
+  const systemChain = api.runtimeChain;
+  // const _metadata: Option<OpaqueMetadata> = await api.call.metadata.metadataAtVersion(15);
+  // const metadataHex = _metadata.isSome ? _metadata.unwrap().toHex().slice(2) : ''; // Need unwrap to create metadata object
+  const metadataHex = api.runtimeMetadata.toHex();
+  const registry = api.registry;
+
+  const updateMetadata = {
+    chain: chain,
+    genesisHash: genesisHash,
+    specName: specName,
+    specVersion: currentSpecVersion,
+    hexValue: metadataHex,
+    types: getSpecTypes(api.registry, systemChain, api.runtimeVersion.specName, api.runtimeVersion.specVersion) as unknown as Record<string, string>,
+    userExtensions: getSpecExtensions(api.registry, systemChain, api.runtimeVersion.specName),
+    ss58Format: registry.chainSS58,
+    tokenDecimals: registry.chainDecimals[0],
+    tokenSymbol: registry.chainTokens[0]
+  };
+
+  chainService?.upsertMetadata(chain, { ...updateMetadata }).catch(console.error);
 };
 
 export const cacheMetadata = (
@@ -57,7 +81,6 @@ export const cacheMetadata = (
   // Update metadata to database with async methods
   substrateApi.api.isReady.then(async (api) => {
     const currentSpecVersion = api.runtimeVersion.specVersion.toString();
-    const specName = api.runtimeVersion.specName.toString();
     const genesisHash = api.genesisHash.toHex();
     const metadata = await chainService?.getMetadata(chain);
 
@@ -66,22 +89,7 @@ export const cacheMetadata = (
       return;
     }
 
-    const systemChain = api.runtimeChain;
-    // const _metadata: Option<OpaqueMetadata> = await api.call.metadata.metadataAtVersion(15);
-    // const metadataHex = _metadata.isSome ? _metadata.unwrap().toHex().slice(2) : ''; // Need unwrap to create metadata object
-    const metadataHex = api.runtimeMetadata.toHex();
-    const metadataV15 = getMetadataV15(api);
-
-    const updateMetadata = {
-      chain: chain,
-      genesisHash: genesisHash,
-      specName: specName,
-      specVersion: currentSpecVersion,
-      hexValue: metadataHex,
-      types: getSpecTypes(api.registry, systemChain, api.runtimeVersion.specName, api.runtimeVersion.specVersion) as unknown as Record<string, string>,
-      userExtensions: getSpecExtensions(api.registry, systemChain, api.runtimeVersion.specName)
-    };
-
-    chainService?.upsertMetadata(chain, { ...updateMetadata, hexV15: metadataV15 }).catch(console.error);
+    getMetadata(chain, api, currentSpecVersion, genesisHash, chainService);
+    await getMetadataV15(chain, api, chainService);
   }).catch(console.error);
 };

From 592f8ea716e52a6b438f3942849e39cfbe6cbfe7 Mon Sep 17 00:00:00 2001
From: tunghp2002 <atsimet@gmail.com>
Date: Mon, 23 Dec 2024 17:15:38 +0700
Subject: [PATCH 4/7] [Update] Init metadataV15 store

---
 .../src/background/KoniTypes.ts               | 14 +++-
 .../src/koni/background/handlers/Extension.ts |  2 +-
 .../src/koni/background/utils.ts              | 10 +--
 .../src/services/chain-service/index.ts       | 10 ++-
 .../storage-service/DatabaseService.ts        |  4 +-
 .../storage-service/databases/index.ts        |  5 +-
 .../storage-service/db-stores/MetadataV15.ts  | 24 ++++++
 .../storage-service/db-stores/index.ts        |  2 +
 packages/extension-base/src/utils/metadata.ts | 80 +++++++++++++------
 9 files changed, 114 insertions(+), 37 deletions(-)
 create mode 100644 packages/extension-base/src/services/storage-service/db-stores/MetadataV15.ts

diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts
index b297bc6c8a..2b892a8f47 100644
--- a/packages/extension-base/src/background/KoniTypes.ts
+++ b/packages/extension-base/src/background/KoniTypes.ts
@@ -277,9 +277,17 @@ export interface MetadataItem {
   types: Record<string, Record<string, string> | string>;
   userExtensions?: ExtDef;
   hexV15?: HexString;
-  ss58Format?: number;
-  tokenDecimals?: number;
-  tokenSymbol?: string;
+  tokenInfo?: {
+    ss58Format: number;
+    tokenDecimals: number;
+    tokenSymbol: string;
+  };
+}
+
+export interface MetadataV15Item {
+  genesisHash: string;
+  specVersion: string;
+  hexV15?: HexString;
 }
 
 export interface CrowdloanItem {
diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts
index 00a9267a25..647f0fcefe 100644
--- a/packages/extension-base/src/koni/background/handlers/Extension.ts
+++ b/packages/extension-base/src/koni/background/handlers/Extension.ts
@@ -2563,7 +2563,7 @@ export default class KoniExtension {
       if (allRegistry.length === 0) {
         registry.setSignedExtensions(payload.signedExtensions);
       } else {
-        registry = getSuitableRegistry(allRegistry, payload).metadata;
+        registry = getSuitableRegistry(allRegistry, payload);
       }
     }
 
diff --git a/packages/extension-base/src/koni/background/utils.ts b/packages/extension-base/src/koni/background/utils.ts
index e4ca8a42ba..d435b987bb 100644
--- a/packages/extension-base/src/koni/background/utils.ts
+++ b/packages/extension-base/src/koni/background/utils.ts
@@ -41,9 +41,7 @@ export function getSuitableRegistry (registries: RegistrySource[], payload: Sign
       return a.specVersion - b.specVersion;
     });
 
-  return {
-    metadata: sortedRegistries[0].registry
-  };
+  return sortedRegistries[0].registry;
 }
 
 export function setupApiRegistry (chainInfo: _ChainInfo | undefined, koniState: KoniState): RegistrySource | null {
@@ -71,9 +69,9 @@ export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _Chain
 
   registry.register(metadata.types);
   registry.setChainProperties(registry.createType('ChainProperties', {
-    ss58Format: metadata.ss58Format,
-    tokenDecimals: metadata.tokenDecimals,
-    tokenSymbol: metadata.tokenSymbol
+    ss58Format: metadata.tokenInfo?.ss58Format,
+    tokenDecimals: metadata.tokenInfo?.tokenDecimals,
+    tokenSymbol: metadata.tokenInfo?.tokenSymbol
   }) as unknown as ChainProperties);
   registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);
 
diff --git a/packages/extension-base/src/services/chain-service/index.ts b/packages/extension-base/src/services/chain-service/index.ts
index 5d5e3f7562..424dbd84c8 100644
--- a/packages/extension-base/src/services/chain-service/index.ts
+++ b/packages/extension-base/src/services/chain-service/index.ts
@@ -13,7 +13,7 @@ import { _CHAIN_VALIDATION_ERROR } from '@subwallet/extension-base/services/chai
 import { _ChainApiStatus, _ChainConnectionStatus, _ChainState, _CUSTOM_PREFIX, _DataMap, _EvmApi, _NetworkUpsertParams, _NFT_CONTRACT_STANDARDS, _SMART_CONTRACT_STANDARDS, _SmartContractTokenInfo, _SubstrateApi, _ValidateCustomAssetRequest, _ValidateCustomAssetResponse } from '@subwallet/extension-base/services/chain-service/types';
 import { _isAssetAutoEnable, _isAssetCanPayTxFee, _isAssetFungibleToken, _isChainEnabled, _isCustomAsset, _isCustomChain, _isCustomProvider, _isEqualContractAddress, _isEqualSmartContractAsset, _isLocalToken, _isMantaZkAsset, _isPureEvmChain, _isPureSubstrateChain, _parseAssetRefKey, randomizeProvider, updateLatestChainInfo } from '@subwallet/extension-base/services/chain-service/utils';
 import { EventService } from '@subwallet/extension-base/services/event-service';
-import { IChain, IMetadataItem } from '@subwallet/extension-base/services/storage-service/databases';
+import { IChain, IMetadataItem, IMetadataV15Item } from '@subwallet/extension-base/services/storage-service/databases';
 import DatabaseService from '@subwallet/extension-base/services/storage-service/DatabaseService';
 import AssetSettingStore from '@subwallet/extension-base/stores/AssetSetting';
 import { addLazy, calculateMetadataHash, fetchStaticData, filterAssetsByChainAndType, getShortMetadata, MODULE_SUPPORT } from '@subwallet/extension-base/utils';
@@ -2070,6 +2070,14 @@ export class ChainService {
     return this.dbService.stores.metadata.upsertMetadata(chain, metadata);
   }
 
+  getMetadataV15 (chain: string) {
+    return this.dbService.stores.metadataV15.getMetadata(chain);
+  }
+
+  upsertMetadataV15 (chain: string, metadata: IMetadataV15Item) {
+    return this.dbService.stores.metadataV15.upsertMetadata(chain, metadata);
+  }
+
   getMetadataByHash (hash: string) {
     return this.dbService.stores.metadata.getMetadataByGenesisHash(hash);
   }
diff --git a/packages/extension-base/src/services/storage-service/DatabaseService.ts b/packages/extension-base/src/services/storage-service/DatabaseService.ts
index b0db514b1f..a91949bf05 100644
--- a/packages/extension-base/src/services/storage-service/DatabaseService.ts
+++ b/packages/extension-base/src/services/storage-service/DatabaseService.ts
@@ -6,7 +6,7 @@ import { APIItemState, ChainStakingMetadata, CrowdloanItem, MantaPayConfig, NftC
 import { EventService } from '@subwallet/extension-base/services/event-service';
 import { _NotificationInfo } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
 import KoniDatabase, { IBalance, ICampaign, IChain, ICrowdloanItem, INft } from '@subwallet/extension-base/services/storage-service/databases';
-import { AssetStore, BalanceStore, ChainStore, CrowdloanStore, MetadataStore, MigrationStore, NftCollectionStore, NftStore, PriceStore, StakingStore, TransactionStore } from '@subwallet/extension-base/services/storage-service/db-stores';
+import { AssetStore, BalanceStore, ChainStore, CrowdloanStore, MetadataStore, MetadataV15Store, MigrationStore, NftCollectionStore, NftStore, PriceStore, StakingStore, TransactionStore } from '@subwallet/extension-base/services/storage-service/db-stores';
 import BaseStore from '@subwallet/extension-base/services/storage-service/db-stores/BaseStore';
 import CampaignStore from '@subwallet/extension-base/services/storage-service/db-stores/Campaign';
 import ChainStakingMetadataStore from '@subwallet/extension-base/services/storage-service/db-stores/ChainStakingMetadata';
@@ -55,6 +55,8 @@ export default class DatabaseService {
       migration: new MigrationStore(this._db.migrations),
 
       metadata: new MetadataStore(this._db.metadata),
+      metadataV15: new MetadataV15Store(this._db.metadataV15),
+
       chain: new ChainStore(this._db.chain),
       asset: new AssetStore(this._db.asset),
 
diff --git a/packages/extension-base/src/services/storage-service/databases/index.ts b/packages/extension-base/src/services/storage-service/databases/index.ts
index 1cda2f9af1..9f4c525870 100644
--- a/packages/extension-base/src/services/storage-service/databases/index.ts
+++ b/packages/extension-base/src/services/storage-service/databases/index.ts
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 import { _AssetRef, _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types';
-import { CampaignData, ChainStakingMetadata, CrowdloanItem, MetadataItem, NftCollection, NftItem, NominatorMetadata, PriceJson, StakingItem, TransactionHistoryItem } from '@subwallet/extension-base/background/KoniTypes';
+import { CampaignData, ChainStakingMetadata, CrowdloanItem, MetadataItem, MetadataV15Item, NftCollection, NftItem, NominatorMetadata, PriceJson, StakingItem, TransactionHistoryItem } from '@subwallet/extension-base/background/KoniTypes';
 import { _NotificationInfo } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
 import { BalanceItem, YieldPoolInfo, YieldPositionInfo } from '@subwallet/extension-base/types';
 import Dexie, { Table, Transaction } from 'dexie';
@@ -41,6 +41,7 @@ export interface IMigration {
 }
 
 export interface IMetadataItem extends MetadataItem, DefaultChainDoc {}
+export interface IMetadataV15Item extends MetadataV15Item, DefaultChainDoc {}
 
 export type IMantaPayLedger = any;
 
@@ -62,6 +63,8 @@ export default class KoniDatabase extends Dexie {
   public migrations!: Table<IMigration, object>;
 
   public metadata!: Table<IMetadataItem, object>;
+  public metadataV15!: Table<IMetadataV15Item, object>;
+
   public chain!: Table<IChain, object>;
   public asset!: Table<_ChainAsset, object>;
 
diff --git a/packages/extension-base/src/services/storage-service/db-stores/MetadataV15.ts b/packages/extension-base/src/services/storage-service/db-stores/MetadataV15.ts
new file mode 100644
index 0000000000..a985c2927e
--- /dev/null
+++ b/packages/extension-base/src/services/storage-service/db-stores/MetadataV15.ts
@@ -0,0 +1,24 @@
+// Copyright 2019-2022 @subwallet/extension-base authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import BaseStoreWithChain from '@subwallet/extension-base/services/storage-service/db-stores/BaseStoreWithChain';
+
+import { IMetadataV15Item } from '../databases';
+
+export default class MetadataV15Store extends BaseStoreWithChain<IMetadataV15Item> {
+  getMetadata (chain: string) {
+    return this.table.where('chain').equals(chain).first();
+  }
+
+  upsertMetadata (chain: string, metadata: IMetadataV15Item) {
+    return this.table.put(metadata, chain);
+  }
+
+  getMetadataByGenesisHash (genesisHash: string) {
+    return this.table.get(genesisHash);
+  }
+
+  updateMetadataByGenesisHash (genesisHash: string, metadata: IMetadataV15Item) {
+    return this.table.put(metadata, genesisHash);
+  }
+}
diff --git a/packages/extension-base/src/services/storage-service/db-stores/index.ts b/packages/extension-base/src/services/storage-service/db-stores/index.ts
index f26e20b498..d3e465eaaf 100644
--- a/packages/extension-base/src/services/storage-service/db-stores/index.ts
+++ b/packages/extension-base/src/services/storage-service/db-stores/index.ts
@@ -11,5 +11,7 @@ export { default as TransactionStore } from './Transaction';
 export { default as MigrationStore } from './Migration';
 
 export { default as MetadataStore } from './Metadata';
+export { default as MetadataV15Store } from './MetadataV15';
+
 export { default as ChainStore } from './Chain';
 export { default as AssetStore } from './Asset';
diff --git a/packages/extension-base/src/utils/metadata.ts b/packages/extension-base/src/utils/metadata.ts
index 5e5db113bd..01653171a8 100644
--- a/packages/extension-base/src/utils/metadata.ts
+++ b/packages/extension-base/src/utils/metadata.ts
@@ -5,11 +5,26 @@ import { ChainService } from '@subwallet/extension-base/services/chain-service';
 import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types';
 
 import { ApiPromise } from '@polkadot/api';
+import { TypeRegistry } from '@polkadot/types';
 import { getSpecExtensions, getSpecTypes } from '@polkadot/types-known';
-import { u8aToHex } from '@polkadot/util';
+import { formatBalance, isNumber, u8aToHex } from '@polkadot/util';
 import { HexString } from '@polkadot/util/types';
+import { defaults as addressDefaults } from '@polkadot/util-crypto/address/defaults';
 import { ExtraInfo, merkleizeMetadata } from '@polkadot-api/merkleize-metadata';
 
+interface Statics {
+  api: ApiPromise;
+  registry: TypeRegistry;
+}
+
+export const statics = {
+  api: undefined,
+  registry: new TypeRegistry()
+} as unknown as Statics;
+
+export const DEFAULT_DECIMALS = statics.registry.createType('u32', 12);
+export const DEFAULT_SS58 = statics.registry.createType('u32', addressDefaults.prefix);
+
 export const _isRuntimeUpdated = (signedExtensions?: string[]): boolean => {
   return signedExtensions ? signedExtensions.includes('CheckMetadataHash') : false;
 };
@@ -28,19 +43,29 @@ export const getShortMetadata = (blob: HexString, extraInfo: ExtraInfo, metadata
 
 const getMetadataV15 = async (chain: string, api: ApiPromise, chainService?: ChainService): Promise<void> => {
   try {
+    const currentSpecVersion = api.runtimeVersion.specVersion.toString();
+    const genesisHash = api.genesisHash.toHex();
+    const metadata = await chainService?.getMetadataV15(chain);
+
+    // Avoid date existed metadata
+    if (metadata && metadata.specVersion === currentSpecVersion && metadata.genesisHash === genesisHash) {
+      return;
+    }
+
     if (api.call.metadata.metadataAtVersion) {
       const metadataV15 = await api.call.metadata.metadataAtVersion(15);
 
       if (!metadataV15.isEmpty) {
-        const hexValue = metadataV15.unwrap().toHex();
+        const hexV15 = metadataV15.unwrap().toHex();
+        const updateMetadata = {
+          chain: chain,
+          genesisHash: genesisHash,
+          specVersion: currentSpecVersion,
+          hexV15
 
-        if (chainService) {
-          const metadata = await chainService.getMetadata(chain);
+        };
 
-          if (metadata) {
-            await chainService.upsertMetadata(chain, { ...metadata, hexV15: hexValue });
-          }
-        }
+        chainService?.upsertMetadataV15(chain, { ...updateMetadata }).catch(console.error);
       }
     }
   } catch (err) {
@@ -48,15 +73,33 @@ const getMetadataV15 = async (chain: string, api: ApiPromise, chainService?: Cha
   }
 };
 
-const getMetadata = (chain: string, api: ApiPromise, currentSpecVersion: string, genesisHash: HexString, chainService?: ChainService) => {
+const getMetadata = async (
+  chain: string,
+  api: ApiPromise,
+  chainService?: ChainService
+) => {
+  const currentSpecVersion = api.runtimeVersion.specVersion.toString();
+  const genesisHash = api.genesisHash.toHex();
   const specName = api.runtimeVersion.specName.toString();
+  const metadata = await chainService?.getMetadata(chain);
+
+  // Avoid date existed metadata
+  if (metadata && metadata.specVersion === currentSpecVersion && metadata.genesisHash === genesisHash) {
+    return;
+  }
 
   const systemChain = api.runtimeChain;
-  // const _metadata: Option<OpaqueMetadata> = await api.call.metadata.metadataAtVersion(15);
-  // const metadataHex = _metadata.isSome ? _metadata.unwrap().toHex().slice(2) : ''; // Need unwrap to create metadata object
   const metadataHex = api.runtimeMetadata.toHex();
   const registry = api.registry;
 
+  const tokenInfo = {
+    ss58Format: isNumber(registry.chainSS58)
+      ? registry.chainSS58
+      : DEFAULT_SS58.toNumber(),
+    tokenDecimals: (registry.chainDecimals || [DEFAULT_DECIMALS.toNumber()])[0],
+    tokenSymbol: (registry.chainTokens || formatBalance.getDefaults().unit)[0]
+  };
+
   const updateMetadata = {
     chain: chain,
     genesisHash: genesisHash,
@@ -65,9 +108,7 @@ const getMetadata = (chain: string, api: ApiPromise, currentSpecVersion: string,
     hexValue: metadataHex,
     types: getSpecTypes(api.registry, systemChain, api.runtimeVersion.specName, api.runtimeVersion.specVersion) as unknown as Record<string, string>,
     userExtensions: getSpecExtensions(api.registry, systemChain, api.runtimeVersion.specName),
-    ss58Format: registry.chainSS58,
-    tokenDecimals: registry.chainDecimals[0],
-    tokenSymbol: registry.chainTokens[0]
+    tokenInfo
   };
 
   chainService?.upsertMetadata(chain, { ...updateMetadata }).catch(console.error);
@@ -80,16 +121,7 @@ export const cacheMetadata = (
 ): void => {
   // Update metadata to database with async methods
   substrateApi.api.isReady.then(async (api) => {
-    const currentSpecVersion = api.runtimeVersion.specVersion.toString();
-    const genesisHash = api.genesisHash.toHex();
-    const metadata = await chainService?.getMetadata(chain);
-
-    // Avoid date existed metadata
-    if (metadata && metadata.specVersion === currentSpecVersion && metadata.genesisHash === genesisHash) {
-      return;
-    }
-
-    getMetadata(chain, api, currentSpecVersion, genesisHash, chainService);
+    await getMetadata(chain, api, chainService);
     await getMetadataV15(chain, api, chainService);
   }).catch(console.error);
 };

From 070518e478abc69b2a3dfd1304b82563e2884dcb Mon Sep 17 00:00:00 2001
From: tunghp2002 <atsimet@gmail.com>
Date: Tue, 24 Dec 2024 09:35:34 +0700
Subject: [PATCH 5/7] [Update] Refactor code

---
 .../src/koni/background/utils.ts              |  8 ++----
 .../src/services/chain-service/index.ts       | 26 ++++++++++---------
 packages/extension-base/src/utils/metadata.ts | 11 ++++----
 3 files changed, 21 insertions(+), 24 deletions(-)

diff --git a/packages/extension-base/src/koni/background/utils.ts b/packages/extension-base/src/koni/background/utils.ts
index d435b987bb..3ad32601d2 100644
--- a/packages/extension-base/src/koni/background/utils.ts
+++ b/packages/extension-base/src/koni/background/utils.ts
@@ -38,7 +38,7 @@ export function getSuitableRegistry (registries: RegistrySource[], payload: Sign
         return a.distance - b.distance;
       }
 
-      return a.specVersion - b.specVersion;
+      return b.specVersion - a.specVersion;
     });
 
   return sortedRegistries[0].registry;
@@ -68,11 +68,7 @@ export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _Chain
   const _metadata = new Metadata(registry, metadata.hexValue);
 
   registry.register(metadata.types);
-  registry.setChainProperties(registry.createType('ChainProperties', {
-    ss58Format: metadata.tokenInfo?.ss58Format,
-    tokenDecimals: metadata.tokenInfo?.tokenDecimals,
-    tokenSymbol: metadata.tokenInfo?.tokenSymbol
-  }) as unknown as ChainProperties);
+  registry.setChainProperties(registry.createType('ChainProperties', metadata.tokenInfo) as unknown as ChainProperties);
   registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);
 
   return {
diff --git a/packages/extension-base/src/services/chain-service/index.ts b/packages/extension-base/src/services/chain-service/index.ts
index 424dbd84c8..accbad33e5 100644
--- a/packages/extension-base/src/services/chain-service/index.ts
+++ b/packages/extension-base/src/services/chain-service/index.ts
@@ -3,7 +3,7 @@
 
 import { AssetLogoMap, AssetRefMap, ChainAssetMap, ChainInfoMap, ChainLogoMap, MultiChainAssetMap } from '@subwallet/chain-list';
 import { _AssetRef, _AssetRefPath, _AssetType, _ChainAsset, _ChainInfo, _ChainStatus, _EvmInfo, _MultiChainAsset, _SubstrateChainType, _SubstrateInfo, _TonInfo } from '@subwallet/chain-list/types';
-import { AssetSetting, ValidateNetworkResponse } from '@subwallet/extension-base/background/KoniTypes';
+import { AssetSetting, MetadataItem, ValidateNetworkResponse } from '@subwallet/extension-base/background/KoniTypes';
 import { _DEFAULT_ACTIVE_CHAINS, _ZK_ASSET_PREFIX, LATEST_CHAIN_DATA_FETCHING_INTERVAL } from '@subwallet/extension-base/services/chain-service/constants';
 import { EvmChainHandler } from '@subwallet/extension-base/services/chain-service/handler/EvmChainHandler';
 import { MantaPrivateHandler } from '@subwallet/extension-base/services/chain-service/handler/manta/MantaPrivateHandler';
@@ -2082,42 +2082,44 @@ export class ChainService {
     return this.dbService.stores.metadata.getMetadataByGenesisHash(hash);
   }
 
-  getExtraInfo (chain: string): Omit<ExtraInfo, 'specVersion' | 'specName'> {
-    const chainInfo = this.getChainInfoByKey(chain);
+  getExtraInfo (metadata: MetadataItem): Omit<ExtraInfo, 'specVersion' | 'specName'> {
+    const tokenInfo = metadata.tokenInfo;
 
     return {
-      decimals: chainInfo.substrateInfo?.decimals ?? 0,
-      tokenSymbol: chainInfo.substrateInfo?.symbol ?? 'Unit',
-      base58Prefix: chainInfo.substrateInfo?.addressPrefix ?? 42
+      decimals: tokenInfo?.tokenDecimals ?? 0,
+      tokenSymbol: tokenInfo?.tokenSymbol ?? 'Unit',
+      base58Prefix: tokenInfo?.ss58Format ?? 42
     };
   }
 
   async calculateMetadataHash (chain: string): Promise<string | undefined> {
     const metadata = await this.getMetadata(chain);
+    const metadataV15 = await this.getMetadataV15(chain);
 
-    if (!metadata || !metadata.hexV15) {
+    if (!metadata || !metadataV15 || !metadataV15.hexV15) {
       return undefined;
     }
 
-    const extraInfo = this.getExtraInfo(chain);
+    const extraInfo = this.getExtraInfo(metadata);
     const specVersion = parseInt(metadata.specVersion);
     const specName = metadata.specName;
-    const hexV15 = metadata.hexV15;
+    const hexV15 = metadataV15.hexV15;
 
     return calculateMetadataHash({ ...extraInfo, specVersion, specName }, hexV15);
   }
 
   async shortenMetadata (chain: string, txBlob: string): Promise<string | undefined> {
     const metadata = await this.getMetadata(chain);
+    const metadataV15 = await this.getMetadataV15(chain);
 
-    if (!metadata || !metadata.hexV15) {
+    if (!metadata || !metadataV15 || !metadataV15.hexV15) {
       return undefined;
     }
 
-    const extraInfo = this.getExtraInfo(chain);
+    const extraInfo = this.getExtraInfo(metadata);
     const specVersion = parseInt(metadata.specVersion);
     const specName = metadata.specName;
-    const hexV15 = metadata.hexV15;
+    const hexV15 = metadataV15.hexV15;
 
     return getShortMetadata(txBlob as HexString, { ...extraInfo, specVersion, specName }, hexV15);
   }
diff --git a/packages/extension-base/src/utils/metadata.ts b/packages/extension-base/src/utils/metadata.ts
index 01653171a8..8997baa64e 100644
--- a/packages/extension-base/src/utils/metadata.ts
+++ b/packages/extension-base/src/utils/metadata.ts
@@ -41,7 +41,7 @@ export const getShortMetadata = (blob: HexString, extraInfo: ExtraInfo, metadata
   return u8aToHex(_merkleizeMetadata.getProofForExtrinsicPayload(blob));
 };
 
-const getMetadataV15 = async (chain: string, api: ApiPromise, chainService?: ChainService): Promise<void> => {
+const updateMetadataV15 = async (chain: string, api: ApiPromise, chainService?: ChainService): Promise<void> => {
   try {
     const currentSpecVersion = api.runtimeVersion.specVersion.toString();
     const genesisHash = api.genesisHash.toHex();
@@ -62,7 +62,6 @@ const getMetadataV15 = async (chain: string, api: ApiPromise, chainService?: Cha
           genesisHash: genesisHash,
           specVersion: currentSpecVersion,
           hexV15
-
         };
 
         chainService?.upsertMetadataV15(chain, { ...updateMetadata }).catch(console.error);
@@ -73,7 +72,7 @@ const getMetadataV15 = async (chain: string, api: ApiPromise, chainService?: Cha
   }
 };
 
-const getMetadata = async (
+const updateMetadata = async (
   chain: string,
   api: ApiPromise,
   chainService?: ChainService
@@ -120,8 +119,8 @@ export const cacheMetadata = (
   chainService?: ChainService
 ): void => {
   // Update metadata to database with async methods
-  substrateApi.api.isReady.then(async (api) => {
-    await getMetadata(chain, api, chainService);
-    await getMetadataV15(chain, api, chainService);
+  substrateApi.api.isReady.then((api) => {
+    return updateMetadata(chain, api, chainService)
+      .then(() => updateMetadataV15(chain, api, chainService));
   }).catch(console.error);
 };

From e112761aa5b3f19ceb8fd048e416ea9452481fa8 Mon Sep 17 00:00:00 2001
From: tunghp2002 <atsimet@gmail.com>
Date: Tue, 24 Dec 2024 11:32:50 +0700
Subject: [PATCH 6/7] [Update] Add metadata conditionalVersion

---
 .../src/services/storage-service/databases/index.ts           | 4 ++++
 packages/extension-base/src/utils/metadata.ts                 | 4 ++--
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/packages/extension-base/src/services/storage-service/databases/index.ts b/packages/extension-base/src/services/storage-service/databases/index.ts
index 9f4c525870..0b21de49fc 100644
--- a/packages/extension-base/src/services/storage-service/databases/index.ts
+++ b/packages/extension-base/src/services/storage-service/databases/index.ts
@@ -129,6 +129,10 @@ export default class KoniDatabase extends Dexie {
     this.conditionalVersion(7, {
       inappNotification: 'id, address, proxyId, [proxyId+actionType], actionType'
     });
+
+    this.conditionalVersion(8, {
+      metadataV15: 'genesisHash, chain'
+    });
   }
 
   private conditionalVersion (
diff --git a/packages/extension-base/src/utils/metadata.ts b/packages/extension-base/src/utils/metadata.ts
index 8997baa64e..05e5555936 100644
--- a/packages/extension-base/src/utils/metadata.ts
+++ b/packages/extension-base/src/utils/metadata.ts
@@ -120,7 +120,7 @@ export const cacheMetadata = (
 ): void => {
   // Update metadata to database with async methods
   substrateApi.api.isReady.then((api) => {
-    return updateMetadata(chain, api, chainService)
-      .then(() => updateMetadataV15(chain, api, chainService));
+    updateMetadata(chain, api, chainService).catch(console.error);
+    updateMetadataV15(chain, api, chainService).catch(console.error);
   }).catch(console.error);
 };

From f1943f06b6d563a577c0baec658462c31016f550 Mon Sep 17 00:00:00 2001
From: tunghp2002 <atsimet@gmail.com>
Date: Tue, 24 Dec 2024 11:46:28 +0700
Subject: [PATCH 7/7] [Update] upversion clearmetadataDatabase

---
 .../src/services/migration-service/scripts/index.ts           | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/extension-base/src/services/migration-service/scripts/index.ts b/packages/extension-base/src/services/migration-service/scripts/index.ts
index 03be614d84..301b328477 100644
--- a/packages/extension-base/src/services/migration-service/scripts/index.ts
+++ b/packages/extension-base/src/services/migration-service/scripts/index.ts
@@ -62,9 +62,9 @@ export default <Record<string, typeof BaseMigrationJob>>{
   '1.2.28-02': MigrateTransactionHistoryBySymbol,
   '1.2.69-01': MigrateRemoveGenesisHash,
   '1.2.13-01': ReloadMetadata,
-  '1.2.14-01': ClearMetadataDatabase,
   '1.2.32-01': MigratePairData,
-  '1.3.6-01': MigrateTransactionHistoryBridge
+  '1.3.6-01': MigrateTransactionHistoryBridge,
+  '1.3.10-01': ClearMetadataDatabase
   // [`${EVERYTIME}-1.1.42-02`]: MigrateTransactionHistoryBySymbol
   // [`${EVERYTIME}-1`]: AutoEnableChainsTokens
 };