import { delay , inject , singleton } from "tsyringe" ; import { TYPES } from "../types" ; import { ChainInfoSchema , ChainInfoWithEmbed } from "./types" ; import { ChainInfo } from "@keplr-wallet/types" ; import { KVStore , Debouncer } from "@keplr-wallet/common" ; import { ChainUpdaterService } from "../updater" ; import { InteractionService } from "../interaction" ; import { Env } from "@keplr-wallet/router" ; import { SuggestChainInfoMsg } from "./messages" ; import { ChainIdHelper } from "@keplr-wallet/cosmos" ; type ChainRemovedHandler = ( chainId : string , identifier : string ) => void ; @ singleton ( ) export class ChainsService { protected onChainRemovedHandlers : ChainRemovedHandler [ ] = [ ] ; protected cachedChainInfos : ChainInfoWithEmbed [ ] | undefined ; constructor ( @ inject ( TYPES . ChainsStore ) protected readonly kvStore , @ inject ( TYPES . ChainsEmbedChainInfos ) protected readonly embedChainInfos , @ inject ( delay ( ( ) => ChainUpdaterService ) ) protected readonly chainUpdaterKeeper , @ inject ( delay ( ( ) => InteractionService ) ) protected readonly interactionKeeper ) { } readonly getChainInfos : ( ) => Promise < ChainInfoWithEmbed [ ] > = Debouncer . promise ( async ( ) => { if ( this . cachedChainInfos ) { return this . cachedChainInfos ; } const chainInfos = this . embedChainInfos . map ( ( chainInfo ) => { return { ... chainInfo , embeded : true , } ; } ) ; const embedChainInfoIdentifierMap = new Map ( ) ; for ( const embedChainInfo of chainInfos ) { embedChainInfoIdentifierMap . set ( ChainIdHelper . parse ( embedChainInfo . chainId ) . identifier , true ) ; } const savedChainInfos = ( ( await this . kvStore . get < ChainInfo [ ] > ( "chain-infos" ) ) ?? [ ] ) . filter ( ( chainInfo ) => { return ! embedChainInfoIdentifierMap . get ( ChainIdHelper . parse ( chainInfo . chainId ) . identifier ) ; } ) . map ( ( chainInfo ) => { return { ... chainInfo , embeded : false , } ; } ) ; let result = chainInfos . concat ( savedChainInfos ) ; result = await Promise . all ( result . map ( async ( chainInfo ) => { const updated = await this . chainUpdaterKeeper . putUpdatedPropertyToChainInfo ( chainInfo ) ; return { ... updated , embeded : chainInfo . embeded , } ; } ) ) ; this . cachedChainInfos = result ; return result ; } ) ; clearCachedChainInfos ( ) { this . cachedChainInfos = undefined ; } async getChainInfo ( chainId ) { const chainInfo = ( await this . getChainInfos ( ) ) . find ( ( chainInfo ) => { return ( ChainIdHelper . parse ( chainInfo . chainId ) . identifier === ChainIdHelper . parse ( chainId ) . identifier ) ; } ) ; if ( ! chainInfo ) { throw new Error ( `There is no chain info for ${ chainId }` ) ; } return chainInfo ; } async getChainCoinType ( chainId ) { const chainInfo = await this . getChainInfo ( chainId ) ; if ( ! chainInfo ) { throw new Error ( `There is no chain info for ${ chainId }` ) ; } return chainInfo . bip44 . coinType ; } async hasChainInfo ( chainId ) { return ( ( await this . getChainInfos ( ) ) . find ( ( chainInfo ) => { return ( ChainIdHelper . parse ( chainInfo . chainId ) . identifier === ChainIdHelper . parse ( chainId ) . identifier ) ; } ) != null ) ; } async suggestChainInfo ( env , chainInfo , origin ) { chainInfo = await ChainInfoSchema . validateAsync ( chainInfo , { stripUnknown : true , } ) ; await this . interactionKeeper . waitApprove ( env , "/suggest-chain" , SuggestChainInfoMsg . type ( ) , { ... chainInfo , origin , } ) ; await this . addChainInfo ( chainInfo ) ; } async addChainInfo ( chainInfo ) { if ( await this . hasChainInfo ( chainInfo . chainId ) ) { throw new Error ( "Same chain is already registered" ) ; } const savedChainInfos = ( await this . kvStore . get < ChainInfo [ ] > ( "chain-infos" ) ) ?? [ ] ; savedChainInfos . push ( chainInfo ) ; await this . kvStore . set < ChainInfo [ ] > ( "chain-infos" , savedChainInfos ) ; this . clearCachedChainInfos ( ) ; } async removeChainInfo ( chainId ) { if ( ! ( await this . hasChainInfo ( chainId ) ) ) { throw new Error ( "Chain is not registered" ) ; } if ( ( await this . getChainInfo ( chainId ) ) . embeded ) { throw new Error ( "Can't remove the embedded chain" ) ; } const savedChainInfos = ( await this . kvStore . get < ChainInfo [ ] > ( "chain-infos" ) ) ?? [ ] ; const resultChainInfo = savedChainInfos . filter ( ( chainInfo ) => { return ( ChainIdHelper . parse ( chainInfo . chainId ) . identifier !== ChainIdHelper . parse ( chainId ) . identifier ) ; } ) ; await this . kvStore . set < ChainInfo [ ] > ( "chain-infos" , resultChainInfo ) ; await this . chainUpdaterKeeper . clearUpdatedProperty ( chainId ) ; for ( const chainRemovedHandler of this . onChainRemovedHandlers ) { chainRemovedHandler ( chainId , ChainIdHelper . parse ( chainId ) . identifier ) ; } this . clearCachedChainInfos ( ) ; } addChainRemovedHandler ( handler ) { this . onChainRemovedHandlers . push ( handler ) ; } }