import { action, autorun, makeObservable, observable, runInAction, toJS, } from "mobx"; import { KVStore } from "@keplr-wallet/common"; import { ViewAssetToken } from "./types"; import { KeyRingService } from "../keyring"; import { computedFn } from "mobx-utils"; import { ChainIdHelper } from "@keplr-wallet/cosmos"; import { ChainsUIService } from "../chains-ui"; import { ChainsService } from "../chains"; import { VaultService } from "../vault"; const DISABLED_VIEW_ASSET_TOKEN_MAP_KEY = "disabledViewAssetTokenMap"; export class ManageViewAssetTokenService { @observable protected disabledViewAssetTokenMap: Map>> = new Map(); constructor( protected readonly kvStore: KVStore, public readonly keyRingService: KeyRingService, protected readonly vaultService: VaultService, public readonly chainsUIService: ChainsUIService, protected chainsService: ChainsService ) { makeObservable(this); } async init(): Promise { const saved = await this.kvStore.get< Record> >(DISABLED_VIEW_ASSET_TOKEN_MAP_KEY); if (saved) { runInAction(() => { for (const [vaultId, chainRecord] of Object.entries(saved)) { const chainMap: Map> = new Map(); for (const [chainIdentifier, coinArray] of Object.entries( chainRecord )) { chainMap.set(chainIdentifier, new Set(coinArray)); } this.disabledViewAssetTokenMap.set(vaultId, chainMap); } }); } autorun(() => { this.kvStore.set( DISABLED_VIEW_ASSET_TOKEN_MAP_KEY, this.convertFromNestedObservableToJs(this.disabledViewAssetTokenMap) ); }); this.chainsService.addChainRemovedHandler(this.onChainRemoved); this.chainsUIService.addChainUIEnabledChangedHandler( this.onChainUIEnabledChanged ); this.vaultService.addVaultRemovedHandler(this.onVaultRemoved); } protected readonly onVaultRemoved = (type: string, id: string) => { if (type === "keyRing" && this.disabledViewAssetTokenMap.has(id)) { runInAction(() => { this.disabledViewAssetTokenMap.delete(id); }); } }; protected readonly onChainUIEnabledChanged = ( vaultId: string, chainIdentifiers: ReadonlyArray ) => { const targetVaultDisabledChainMap = this.disabledViewAssetTokenMap.get(vaultId); if (!targetVaultDisabledChainMap) { return; } const shouldBeDeletedChainIdentifiers = []; for (const chainIdentifier of targetVaultDisabledChainMap.keys()) { if (!chainIdentifiers.includes(chainIdentifier)) { shouldBeDeletedChainIdentifiers.push(chainIdentifier); } } for (const chainIdentifier of shouldBeDeletedChainIdentifiers) { targetVaultDisabledChainMap.delete(chainIdentifier); } runInAction(() => { this.disabledViewAssetTokenMap.set(vaultId, targetVaultDisabledChainMap); }); }; protected readonly onChainRemoved = (chainId: string) => { //이상하긴 하지만 ChainsUIService과 비슷하게 처리 하도록 구현 //해서 다른 vault에서 체인을 삭제하면 전체 vault에서 삭제됨 const chainIdentifier = ChainIdHelper.parse(chainId).identifier; const vaultIds = this.disabledViewAssetTokenMap.keys(); runInAction(() => { for (const vaultId of vaultIds) { this.disabledViewAssetTokenMap.get(vaultId)?.delete(chainIdentifier); } }); }; getDisabledViewAssetTokenList = computedFn( (vaultId: string): Record => { if (!this.checkIsValidVaultId(vaultId)) { throw new Error("Invalid vault id"); } const chainMap: Map> | undefined = this.disabledViewAssetTokenMap.get(vaultId); if (!chainMap) { runInAction(() => { this.disabledViewAssetTokenMap.set(vaultId, new Map()); }); return {}; } const js = toJS(chainMap); const res = Object.fromEntries( Array.from(js.entries()).map(([chainIdentifier, coinSet]) => [ chainIdentifier, Array.from(coinSet), ]) ); return res; }, { keepAlive: true, } ); getAllDisabledViewAssetTokenList = computedFn( (): Record> => { const res = this.convertFromNestedObservableToJs( this.disabledViewAssetTokenMap ); return res; }, { keepAlive: true, } ); @action disableViewAssetToken( vaultId: string, token: ViewAssetToken ): Record> { if (!this.checkIsValidVaultId(vaultId)) { throw new Error("Invalid vault id"); } if (!this.disabledViewAssetTokenMap.has(vaultId)) { runInAction(() => { this.disabledViewAssetTokenMap.set( vaultId, new Map>() ); }); } const chainIdentifier: string = ChainIdHelper.parse( token.chainId ).identifier; const previousMap = this.disabledViewAssetTokenMap.get(vaultId) || new Map>(); let coinSet: Set | undefined = previousMap?.get(chainIdentifier); if (!coinSet) { coinSet = new Set(); } coinSet.add(token.coinMinimalDenom); previousMap?.set(chainIdentifier, coinSet); this.disabledViewAssetTokenMap.set(vaultId, previousMap); const res = this.convertFromNestedObservableToJs( this.disabledViewAssetTokenMap ); return res; } @action enableViewAssetToken( vaultId: string, token: ViewAssetToken ): Record> { if (!this.checkIsValidVaultId(vaultId)) { throw new Error("Invalid vault id"); } const previousMap: Map> | undefined = this.disabledViewAssetTokenMap.get(vaultId); if (!previousMap) { const newMap = new Map>(); this.disabledViewAssetTokenMap.set(vaultId, newMap); return this.convertFromNestedObservableToJs( this.disabledViewAssetTokenMap ); } const chainIdentifier: string = ChainIdHelper.parse( token.chainId ).identifier; const coinSet: Set | undefined = previousMap.get(chainIdentifier); if (!coinSet) { return this.convertFromNestedObservableToJs( this.disabledViewAssetTokenMap ); } coinSet.delete(token.coinMinimalDenom); this.disabledViewAssetTokenMap.set(vaultId, previousMap); return this.convertFromNestedObservableToJs(this.disabledViewAssetTokenMap); } protected convertFromNestedObservableToJs( data: Map>> ): Record> { const js = toJS(data); return Object.fromEntries( Array.from(js.entries()).map(([vaultId, chainMap]) => [ vaultId, Object.fromEntries( Array.from(chainMap.entries()).map(([chainIdentifier, coinSet]) => [ chainIdentifier, Array.from(coinSet), ]) ), ]) ); } protected checkIsValidVaultId(vaultId: string): boolean { return this.keyRingService .getKeyRingVaults() .some((vault) => vault.id === vaultId); } }