diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index aa0eb49668..fddcb10481 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -38,6 +38,7 @@ jobs: KEPLR_EXT_TRANSAK_API_KEY: ${{ secrets.KEPLR_EXT_TRANSAK_API_KEY }} KEPLR_EXT_MOONPAY_API_KEY: ${{ secrets.KEPLR_EXT_MOONPAY_API_KEY }} KEPLR_EXT_KADO_API_KEY: ${{ secrets.KEPLR_EXT_KADO_API_KEY }} + KEPLR_EXT_CHAIN_REGISTRY_URL: ${{ secrets.KEPLR_EXT_CHAIN_REGISTRY_URL }} - run: sudo apt-get install gh - run: npx zx ./scripts/publish.mjs env: @@ -63,7 +64,7 @@ jobs: - run: cp ./build/firefox-archive.tar.gz ../firefox-archive.tar.gz - run: mkdir temp && tar xvzf firefox-archive.tar.gz -C temp working-directory: .. - - run: docker build -f ./docker/Dockerfile -t builder --build-arg KEPLR_EXT_ETHEREUM_ENDPOINT=$KEPLR_EXT_ETHEREUM_ENDPOINT --build-arg KEPLR_EXT_AMPLITUDE_API_KEY=$KEPLR_EXT_AMPLITUDE_API_KEY --build-arg KEPLR_EXT_ANALYTICS_API_AUTH_TOKEN=$KEPLR_EXT_ANALYTICS_API_AUTH_TOKEN --build-arg KEPLR_EXT_ANALYTICS_API_URL=$KEPLR_EXT_ANALYTICS_API_URL --build-arg KEPLR_EXT_COINGECKO_ENDPOINT=$KEPLR_EXT_COINGECKO_ENDPOINT --build-arg KEPLR_EXT_COINGECKO_GETPRICE=$KEPLR_EXT_COINGECKO_GETPRICE --build-arg KEPLR_EXT_TRANSAK_API_KEY=$KEPLR_EXT_TRANSAK_API_KEY --build-arg KEPLR_EXT_MOONPAY_API_KEY=$KEPLR_EXT_MOONPAY_API_KEY --build-arg KEPLR_EXT_KADO_API_KEY=$KEPLR_EXT_KADO_API_KEY . + - run: docker build -f ./docker/Dockerfile -t builder --build-arg KEPLR_EXT_ETHEREUM_ENDPOINT=$KEPLR_EXT_ETHEREUM_ENDPOINT --build-arg KEPLR_EXT_AMPLITUDE_API_KEY=$KEPLR_EXT_AMPLITUDE_API_KEY --build-arg KEPLR_EXT_ANALYTICS_API_AUTH_TOKEN=$KEPLR_EXT_ANALYTICS_API_AUTH_TOKEN --build-arg KEPLR_EXT_ANALYTICS_API_URL=$KEPLR_EXT_ANALYTICS_API_URL --build-arg KEPLR_EXT_COINGECKO_ENDPOINT=$KEPLR_EXT_COINGECKO_ENDPOINT --build-arg KEPLR_EXT_COINGECKO_GETPRICE=$KEPLR_EXT_COINGECKO_GETPRICE --build-arg KEPLR_EXT_TRANSAK_API_KEY=$KEPLR_EXT_TRANSAK_API_KEY --build-arg KEPLR_EXT_MOONPAY_API_KEY=$KEPLR_EXT_MOONPAY_API_KEY --build-arg KEPLR_EXT_KADO_API_KEY=$KEPLR_EXT_KADO_API_KEY --build-arg KEPLR_EXT_CHAIN_REGISTRY_URL=$KEPLR_EXT_CHAIN_REGISTRY_URL . working-directory: ../temp env: KEPLR_EXT_ETHEREUM_ENDPOINT: ${{ secrets.KEPLR_EXT_ETHEREUM_ENDPOINT }} @@ -75,6 +76,7 @@ jobs: KEPLR_EXT_TRANSAK_API_KEY: ${{ secrets.KEPLR_EXT_TRANSAK_API_KEY }} KEPLR_EXT_MOONPAY_API_KEY: ${{ secrets.KEPLR_EXT_MOONPAY_API_KEY }} KEPLR_EXT_KADO_API_KEY: ${{ secrets.KEPLR_EXT_KADO_API_KEY }} + KEPLR_EXT_CHAIN_REGISTRY_URL: ${{ secrets.KEPLR_EXT_CHAIN_REGISTRY_URL }} - run: docker run -v $(pwd):/data builder working-directory: ../temp - run: sudo apt-get update diff --git a/docker/Dockerfile b/docker/Dockerfile index dbd27e550f..d6f4422024 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -31,6 +31,7 @@ ARG KEPLR_EXT_COINGECKO_GETPRICE ARG KEPLR_EXT_TRANSAK_API_KEY ARG KEPLR_EXT_MOONPAY_API_KEY ARG KEPLR_EXT_KADO_API_KEY +ARG KEPLR_EXT_CHAIN_REGISTRY_URL ENV KEPLR_EXT_ETHEREUM_ENDPOINT $KEPLR_EXT_ETHEREUM_ENDPOINT ENV KEPLR_EXT_AMPLITUDE_API_KEY $KEPLR_EXT_AMPLITUDE_API_KEY @@ -41,6 +42,7 @@ ENV KEPLR_EXT_COINGECKO_GETPRICE $KEPLR_EXT_COINGECKO_GETPRICE ENV KEPLR_EXT_TRANSAK_API_KEY $KEPLR_EXT_TRANSAK_API_KEY ENV KEPLR_EXT_MOONPAY_API_KEY $KEPLR_EXT_MOONPAY_API_KEY ENV KEPLR_EXT_KADO_API_KEY $KEPLR_EXT_KADO_API_KEY +ENV KEPLR_EXT_CHAIN_REGISTRY_URL $KEPLR_EXT_CHAIN_REGISTRY_URL RUN yarn install --immutable diff --git a/lerna.json b/lerna.json index 746f8cf41b..44105018fe 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.12.32", + "version": "0.12.47", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/analytics/package.json b/packages/analytics/package.json index ca3af851b5..9f405e94cd 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -1,6 +1,6 @@ { "name": "@keplr-wallet/analytics", - "version": "0.12.32", + "version": "0.12.47", "main": "build/index.js", "author": "chainapsis", "license": "Apache-2.0", diff --git a/packages/background/package.json b/packages/background/package.json index daa56bd149..ab37af4a44 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@keplr-wallet/background", - "version": "0.12.32", + "version": "0.12.47", "main": "build/index.js", "author": "chainapsis", "license": "Apache-2.0", @@ -28,17 +28,17 @@ "@ethersproject/hash": "^5.7.0", "@ethersproject/transactions": "^5.7.0", "@ethersproject/wallet": "^5.7.0", - "@keplr-wallet/chain-validator": "0.12.32", - "@keplr-wallet/common": "0.12.32", - "@keplr-wallet/cosmos": "0.12.32", - "@keplr-wallet/crypto": "0.12.32", - "@keplr-wallet/ledger-cosmos": "0.12.32", - "@keplr-wallet/popup": "0.12.32", - "@keplr-wallet/proto-types": "0.12.32", - "@keplr-wallet/router": "0.12.32", - "@keplr-wallet/simple-fetch": "0.12.32", - "@keplr-wallet/types": "0.12.32", - "@keplr-wallet/unit": "0.12.32", + "@keplr-wallet/chain-validator": "0.12.47", + "@keplr-wallet/common": "0.12.47", + "@keplr-wallet/cosmos": "0.12.47", + "@keplr-wallet/crypto": "0.12.47", + "@keplr-wallet/ledger-cosmos": "0.12.47", + "@keplr-wallet/popup": "0.12.47", + "@keplr-wallet/proto-types": "0.12.47", + "@keplr-wallet/router": "0.12.47", + "@keplr-wallet/simple-fetch": "0.12.47", + "@keplr-wallet/types": "0.12.47", + "@keplr-wallet/unit": "0.12.47", "@ledgerhq/hw-app-eth": "^6.29.3", "@ledgerhq/hw-transport": "^6.20.0", "@ledgerhq/hw-transport-webhid": "^6.20.0", diff --git a/packages/background/src/analytics/service.ts b/packages/background/src/analytics/service.ts index e299521af6..46de347510 100644 --- a/packages/background/src/analytics/service.ts +++ b/packages/background/src/analytics/service.ts @@ -88,11 +88,15 @@ export class AnalyticsService { v2: true, }) ).toString("base64"); - await simpleFetch(KEPLR_EXT_ANALYTICS_API_URL, `/log?msg=${loggingMsg}`, { - headers: { - Authorization: KEPLR_EXT_ANALYTICS_API_AUTH_TOKEN, - }, - }); + await simpleFetch( + KEPLR_EXT_ANALYTICS_API_URL, + `/log?msg=${encodeURIComponent(loggingMsg)}`, + { + headers: { + Authorization: KEPLR_EXT_ANALYTICS_API_AUTH_TOKEN, + }, + } + ); } logEventIgnoreError( diff --git a/packages/background/src/chains-update/service.ts b/packages/background/src/chains-update/service.ts index 9ff865bf80..a568f4c8c7 100644 --- a/packages/background/src/chains-update/service.ts +++ b/packages/background/src/chains-update/service.ts @@ -117,9 +117,14 @@ export class ChainsUpdateService { let updated1 = false; if (!chainInfo.updateFromRepoDisabled) { - updated1 = await this.chainsService.tryUpdateChainInfoFromRepo( - chainIdentifier - ); + try { + updated1 = await this.chainsService.tryUpdateChainInfoFromRepo( + chainIdentifier + ); + } catch (e) { + console.log(e); + // Ignore error to proceed to tryUpdateChainInfoFromRpcOrRest if it fails. + } } const updated2 = await this.chainsService.tryUpdateChainInfoFromRpcOrRest( diff --git a/packages/background/src/chains/service.ts b/packages/background/src/chains/service.ts index 8e4c1bbcc3..2f9908f89d 100644 --- a/packages/background/src/chains/service.ts +++ b/packages/background/src/chains/service.ts @@ -65,10 +65,14 @@ export class ChainsService { readonly organizationName: string; readonly repoName: string; readonly branchName: string; + readonly alternativeURL?: string; }, protected readonly interactionService: InteractionService, protected readonly afterInitFn: - | ((service: ChainsService) => void | Promise) + | (( + service: ChainsService, + lastEmbedChainInfos: ChainInfoWithCoreTypes[] + ) => void | Promise) | undefined ) { this.updatedChainInfoKVStore = new PrefixKVStore( @@ -237,11 +241,14 @@ export class ChainsService { * 모든 서비스가 init이 된 이후에 실행될 추가적인 로직을 여기에 작성할 수 있다. */ async afterInit(): Promise { + const lastEmbedChainInfos = await this.kvStore.get< + ChainInfoWithCoreTypes[] + >("last_embed_chain_infos"); + if (this.afterInitFn) { - await this.afterInitFn(this); + await this.afterInitFn(this, lastEmbedChainInfos ?? []); } - // 그냥 미래에 필요할지도 몰라서... await this.kvStore.set( "last_embed_chain_infos", toJS(this.embedChainInfos) @@ -352,8 +359,12 @@ export class ChainsService { const chainIdentifier = ChainIdHelper.parse(chainId).identifier; const res = await simpleFetch( - `https://raw.githubusercontent.com/${this.communityChainInfoRepo.organizationName}/${this.communityChainInfoRepo.repoName}/${this.communityChainInfoRepo.branchName}`, - `/cosmos/${chainIdentifier}.json` + this.communityChainInfoRepo.alternativeURL + ? this.communityChainInfoRepo.alternativeURL.replace( + "{chain_identifier}", + chainIdentifier + ) + : `https://raw.githubusercontent.com/${this.communityChainInfoRepo.organizationName}/${this.communityChainInfoRepo.repoName}/${this.communityChainInfoRepo.branchName}/cosmos/${chainIdentifier}.json` ); let chainInfo: ChainInfo = res.data; diff --git a/packages/background/src/index.ts b/packages/background/src/index.ts index b7be8ab9f4..3392ed788b 100644 --- a/packages/background/src/index.ts +++ b/packages/background/src/index.ts @@ -48,6 +48,7 @@ export * from "./recent-send-history"; import { KVStore } from "@keplr-wallet/common"; import { ChainInfo } from "@keplr-wallet/types"; import { Notification } from "./tx"; +import { ChainInfoWithCoreTypes } from "./chains"; export function init( router: Router, @@ -63,6 +64,7 @@ export function init( readonly organizationName: string; readonly repoName: string; readonly branchName: string; + readonly alternativeURL?: string; }, notification: Notification, addDeviceLockedListener: (callback: () => void) => void, @@ -71,7 +73,10 @@ export function init( commonCrypto: KeyRingLegacy.CommonCrypto; readonly getDisabledChainIdentifiers: () => Promise; }, - afterInitFn?: (service: Chains.ChainsService) => void | Promise + afterInitFn?: ( + service: Chains.ChainsService, + lastEmbedChainInfos: ChainInfoWithCoreTypes[] + ) => void | Promise ): { initFn: () => Promise; keyRingService: KeyRingV2.KeyRingService; @@ -147,6 +152,7 @@ export function init( chainsService, interactionService, vaultService, + analyticsService, [ new KeyRingMnemonic.KeyRingMnemonicService(vaultService), new KeyRingLedger.KeyRingLedgerService(), @@ -205,7 +211,8 @@ export function init( new RecentSendHistory.RecentSendHistoryService( storeCreator("recent-send-history"), chainsService, - backgroundTxService + backgroundTxService, + notification ); Interaction.init(router, interactionService); @@ -246,6 +253,8 @@ export function init( return { initFn: async () => { + await analyticsService.init(); + await chainsService.init(); await vaultService.init(); await chainsUIService.init(); @@ -259,7 +268,6 @@ export function init( await backgroundTxService.init(); await phishingListService.init(); await autoLockAccountService.init(); - await analyticsService.init(); await permissionInteractiveService.init(); await secretWasmService.init(); diff --git a/packages/background/src/keyring/service.ts b/packages/background/src/keyring/service.ts index 1a69a64f8f..106f456207 100644 --- a/packages/background/src/keyring/service.ts +++ b/packages/background/src/keyring/service.ts @@ -12,6 +12,7 @@ import { Buffer } from "buffer/"; import * as Legacy from "./legacy"; import { ChainsUIService } from "../chains-ui"; import { MultiAccounts } from "../keyring-keystone"; +import { AnalyticsService } from "../analytics"; export class KeyRingService { protected _needMigration = false; @@ -31,6 +32,7 @@ export class KeyRingService { protected readonly chainsService: ChainsService, protected readonly interactionService: InteractionService, protected readonly vaultService: VaultService, + protected readonly analyticsService: AnalyticsService, protected readonly keyRings: KeyRing[] ) { makeObservable(this); @@ -64,6 +66,34 @@ export class KeyRingService { this.kvStore.set("selectedVaultId", null); } }); + + autorun(() => { + const vaults = this.getKeyRingVaults(); + const numPerTypes: Record = {}; + for (const vault of vaults) { + let type = vault.insensitive["keyRingType"] as string; + if (type === "private-key") { + const meta = vault.insensitive["keyRingMeta"] as PlainObject; + if (meta["web3Auth"] && (meta["web3Auth"] as any)["type"]) { + type = "web3_auth_" + (meta["web3Auth"] as any)["type"]; + } + } + + if (type) { + type = "keyring_" + type + "_num"; + + if (!numPerTypes[type]) { + numPerTypes[type] = 0; + } + numPerTypes[type] += 1; + } + } + + this.analyticsService.logEvent("user_properties", { + keyring_num: vaults.length, + ...numPerTypes, + }); + }); } lockKeyRing(): void { @@ -863,7 +893,6 @@ export class KeyRingService { ); } - @action async deleteKeyRing(vaultId: string, password: string) { if (this.vaultService.isLocked) { throw new Error("KeyRing is locked"); @@ -881,12 +910,14 @@ export class KeyRingService { this.vaultService.removeVault("keyRing", vaultId); if (wasSelected) { - const keyInfos = this.getKeyInfos(); - if (keyInfos.length > 0) { - this._selectedVaultId = keyInfos[0].id; - } else { - this._selectedVaultId = undefined; - } + runInAction(() => { + const keyInfos = this.getKeyInfos(); + if (keyInfos.length > 0) { + this._selectedVaultId = keyInfos[0].id; + } else { + this._selectedVaultId = undefined; + } + }); } if (wasSelected) { diff --git a/packages/background/src/recent-send-history/handler.ts b/packages/background/src/recent-send-history/handler.ts index 122ed62825..819d0a60c9 100644 --- a/packages/background/src/recent-send-history/handler.ts +++ b/packages/background/src/recent-send-history/handler.ts @@ -85,7 +85,10 @@ const handleSendTxAndRecordMsg: ( msg.recipient, msg.amount, msg.memo, - undefined + undefined, + { + currencies: [], + } ); }; }; @@ -105,7 +108,8 @@ const handleSendTxAndRecordWithIBCPacketForwardingMsg: ( msg.recipient, msg.amount, msg.memo, - msg.channels + msg.channels, + msg.notificationInfo ); }; }; @@ -127,7 +131,8 @@ const handleSendTxAndRecordWithIBCSwapMsg: ( msg.channels, msg.destinationAsset, msg.swapChannelIndex, - msg.swapReceiver + msg.swapReceiver, + msg.notificationInfo ); }; }; diff --git a/packages/background/src/recent-send-history/messages.ts b/packages/background/src/recent-send-history/messages.ts index f4b236d658..03e7c042d9 100644 --- a/packages/background/src/recent-send-history/messages.ts +++ b/packages/background/src/recent-send-history/messages.ts @@ -1,6 +1,7 @@ import { Message } from "@keplr-wallet/router"; import { ROUTE } from "./constants"; import { IBCHistory, RecentSendHistory } from "./types"; +import { AppCurrency } from "@keplr-wallet/types"; export class GetRecentSendHistoriesMsg extends Message { public static type() { @@ -102,7 +103,10 @@ export class SendTxAndRecordMsg extends Message { portId: string; channelId: string; counterpartyChainId: string; - }[] + }[], + notificationInfo: { + currencies: AppCurrency[]; + } ): SendTxAndRecordWithIBCPacketForwardingMsg { return new SendTxAndRecordWithIBCPacketForwardingMsg( this.historyType, @@ -115,7 +119,8 @@ export class SendTxAndRecordMsg extends Message { this.sender, this.recipient, this.amount, - this.memo + this.memo, + notificationInfo ); } } @@ -143,7 +148,10 @@ export class SendTxAndRecordWithIBCPacketForwardingMsg extends Message { readonly amount: string; readonly denom: string; }[], - public readonly memo: string + public readonly memo: string, + public readonly notificationInfo: { + currencies: AppCurrency[]; + } ) { super(); } diff --git a/packages/background/src/recent-send-history/service.ts b/packages/background/src/recent-send-history/service.ts index 0174c94af6..715436d541 100644 --- a/packages/background/src/recent-send-history/service.ts +++ b/packages/background/src/recent-send-history/service.ts @@ -4,7 +4,7 @@ import { ChainIdHelper, TendermintTxTracer, } from "@keplr-wallet/cosmos"; -import { BackgroundTxService } from "../tx"; +import { BackgroundTxService, Notification } from "../tx"; import { action, autorun, @@ -16,7 +16,8 @@ import { import { KVStore, retry } from "@keplr-wallet/common"; import { IBCHistory, RecentSendHistory } from "./types"; import { Buffer } from "buffer/"; -import { ChainInfo } from "@keplr-wallet/types"; +import { AppCurrency, ChainInfo } from "@keplr-wallet/types"; +import { CoinPretty } from "@keplr-wallet/unit"; export class RecentSendHistoryService { // Key: {chain_identifier}/{type} @@ -33,7 +34,8 @@ export class RecentSendHistoryService { constructor( protected readonly kvStore: KVStore, protected readonly chainsService: ChainsService, - protected readonly txService: BackgroundTxService + protected readonly txService: BackgroundTxService, + protected readonly notification: Notification ) { makeObservable(this); } @@ -129,7 +131,10 @@ export class RecentSendHistoryService { channelId: string; counterpartyChainId: string; }[] - | undefined + | undefined, + notificationInfo: { + currencies: AppCurrency[]; + } ): Promise { const sourceChainInfo = this.chainsService.getChainInfoOrThrow(sourceChainId); @@ -169,6 +174,7 @@ export class RecentSendHistoryService { amount, memo, ibcChannels, + notificationInfo, txHash ); @@ -202,7 +208,10 @@ export class RecentSendHistoryService { denom: string; }, swapChannelIndex: number, - swapReceiver: string[] + swapReceiver: string[], + notificationInfo: { + currencies: AppCurrency[]; + } ): Promise { const sourceChainInfo = this.chainsService.getChainInfoOrThrow(sourceChainId); @@ -228,6 +237,7 @@ export class RecentSendHistoryService { destinationAsset, swapChannelIndex, swapReceiver, + notificationInfo, txHash ); @@ -299,7 +309,10 @@ export class RecentSendHistoryService { return history.ibcHistory.find((h) => h.error != null) != null; })(); - if (needRewind) { + if ( + needRewind && + !history.ibcHistory.find((h) => h.rewoundButNextRewindingBlocked) + ) { const lastRewoundChannelIndex = history.ibcHistory.findIndex((h) => { if (h.rewound) { return true; @@ -315,6 +328,11 @@ export class RecentSendHistoryService { } return history.ibcHistory.find((h) => h.error != null); })(); + const isSwapTargetChannel = + targetChannel && + "swapChannelIndex" in history && + history.ibcHistory.indexOf(targetChannel) === + history.swapChannelIndex + 1; if (targetChannel && targetChannel.sequence) { const prevChainInfo = (() => { @@ -340,12 +358,50 @@ export class RecentSendHistoryService { txTracer.addEventListener("error", onError); txTracer .traceTx({ - "acknowledge_packet.packet_src_port": targetChannel.portId, + // "acknowledge_packet.packet_src_port": targetChannel.portId, "acknowledge_packet.packet_src_channel": targetChannel.channelId, "acknowledge_packet.packet_sequence": targetChannel.sequence, }) - .then(() => { + .then((res: any) => { + txTracer.close(); + + if (!res) { + return; + } + runInAction(() => { + if (isSwapTargetChannel) { + const txs = res.txs + ? res.txs.map((res: any) => res.tx_result || res) + : [res.tx_result || res]; + if (txs && Array.isArray(txs)) { + for (const tx of txs) { + if (targetChannel.sequence && "swapReceiver" in history) { + const index = + this.getIBCAcknowledgementPacketIndexFromTx( + tx, + targetChannel.portId, + targetChannel.channelId, + targetChannel.sequence + ); + if (index >= 0) { + const refunded = this.getIBCSwapResAmountFromTx( + tx, + history.swapReceiver[history.swapChannelIndex + 1], + index + ); + history.swapRefundInfo = { + chainId: prevChainInfo.chainId, + amount: refunded, + }; + + targetChannel.rewoundButNextRewindingBlocked = true; + break; + } + } + } + } + } targetChannel.rewound = true; }); onFulfill(); @@ -419,7 +475,7 @@ export class RecentSendHistoryService { ); if (chainInfo) { const queryEvents: any = { - "recv_packet.packet_src_port": targetChannel.portId, + // "recv_packet.packet_src_port": targetChannel.portId, "recv_packet.packet_src_channel": targetChannel.channelId, "recv_packet.packet_sequence": targetChannel.sequence, }; @@ -478,29 +534,116 @@ export class RecentSendHistoryService { targetChannel.sequence! ); - if ("swapReceiver" in history) { - const res: { - amount: string; - denom: string; - }[] = this.getIBCSwapResAmountFromTx( - tx, - history.swapReceiver[targetChannelIndex + 1], - index - ); - - history.resAmount.push(res); - } + if (index >= 0) { + if ("swapReceiver" in history) { + const res: { + amount: string; + denom: string; + }[] = this.getIBCSwapResAmountFromTx( + tx, + history.swapReceiver[targetChannelIndex + 1], + index + ); + + history.resAmount.push(res); + } - if (nextChannel) { - nextChannel.sequence = this.getIBCPacketSequenceFromTx( - tx, - nextChannel.portId, - nextChannel.channelId, - index - ); - onFulfill(); - this.trackIBCPacketForwardingRecursive(id); - break; + if (nextChannel) { + nextChannel.sequence = this.getIBCPacketSequenceFromTx( + tx, + nextChannel.portId, + nextChannel.channelId, + index + ); + onFulfill(); + this.trackIBCPacketForwardingRecursive(id); + break; + } else { + // Packet received to destination chain. + if (history.notificationInfo && !history.notified) { + runInAction(() => { + history.notified = true; + }); + + const chainInfo = this.chainsService.getChainInfo( + history.destinationChainId + ); + if (chainInfo) { + if ("swapType" in history) { + if (history.resAmount.length > 0) { + const amount = + history.resAmount[ + history.resAmount.length - 1 + ]; + const assetsText = amount + .filter((amt) => + history.notificationInfo!.currencies.find( + (cur) => + cur.coinMinimalDenom === amt.denom + ) + ) + .map((amt) => { + const currency = + history.notificationInfo!.currencies.find( + (cur) => + cur.coinMinimalDenom === amt.denom + ); + return new CoinPretty(currency!, amt.amount) + .hideIBCMetadata(true) + .shrink(true) + .maxDecimals(6) + .inequalitySymbol(true) + .trim(true) + .toString(); + }); + if (assetsText.length > 0) { + // Notify user + this.notification.create({ + iconRelativeUrl: "assets/logo-256.png", + title: "IBC Swap Succeeded", + message: `${assetsText.join( + ", " + )} received on ${chainInfo.chainName}`, + }); + } + } + } else { + const assetsText = history.amount + .filter((amt) => + history.notificationInfo!.currencies.find( + (cur) => cur.coinMinimalDenom === amt.denom + ) + ) + .map((amt) => { + const currency = + history.notificationInfo!.currencies.find( + (cur) => + cur.coinMinimalDenom === amt.denom + ); + return new CoinPretty(currency!, amt.amount) + .hideIBCMetadata(true) + .shrink(true) + .maxDecimals(6) + .inequalitySymbol(true) + .trim(true) + .toString(); + }); + if (assetsText.length > 0) { + // Notify user + this.notification.create({ + iconRelativeUrl: "assets/logo-256.png", + title: "IBC Transfer Succeeded", + message: `${assetsText.join(", ")} sent to ${ + chainInfo.chainName + }`, + }); + } + } + } + } + onFulfill(); + break; + } } } catch { // noop @@ -554,6 +697,9 @@ export class RecentSendHistoryService { channelId: string; counterpartyChainId: string; }[], + notificationInfo: { + currencies: AppCurrency[]; + }, txHash: Uint8Array ): string { const id = (this.recentIBCHistorySeq++).toString(); @@ -577,6 +723,7 @@ export class RecentSendHistoryService { completed: false, }; }), + notificationInfo, txHash: Buffer.from(txHash).toString("hex"), }; @@ -608,6 +755,9 @@ export class RecentSendHistoryService { }, swapChannelIndex: number, swapReceiver: string[], + notificationInfo: { + currencies: AppCurrency[]; + }, txHash: Uint8Array ): string { const id = (this.recentIBCHistorySeq++).toString(); @@ -635,6 +785,7 @@ export class RecentSendHistoryService { swapChannelIndex, swapReceiver, resAmount: [], + notificationInfo, txHash: Buffer.from(txHash).toString("hex"), }; @@ -868,7 +1019,7 @@ export class RecentSendHistoryService { return []; } - protected getIBCRecvPacketIndexFromTx( + protected getIBCAcknowledgementPacketIndexFromTx( tx: any, sourcePortId: string, sourceChannelId: string, @@ -900,7 +1051,7 @@ export class RecentSendHistoryService { }; const packetEvent = events.find((event: any) => { - if (event.type !== "recv_packet") { + if (event.type !== "acknowledge_packet") { return false; } const sourcePortAttr = event.attributes.find((attr: { key: string }) => { @@ -947,15 +1098,95 @@ export class RecentSendHistoryService { } }); if (!packetEvent) { - throw new Error("Invalid tx"); + return -1; } - const index = events.indexOf(packetEvent); - if (index < 0) { + return events.indexOf(packetEvent); + } + + protected getIBCRecvPacketIndexFromTx( + tx: any, + sourcePortId: string, + sourceChannelId: string, + sequence: string + ): number { + const events = tx.events; + if (!events) { throw new Error("Invalid tx"); } + if (!Array.isArray(events)) { + throw new Error("Invalid tx"); + } + + // In injective, events from tendermint rpc is not encoded as base64. + // I don't know that this is the difference from tendermint version, or just custom from injective. + const compareStringWithBase64OrPlain = ( + target: string, + value: string + ): [boolean, boolean] => { + if (target === value) { + return [true, false]; + } + + if (target === Buffer.from(value).toString("base64")) { + return [true, true]; + } + + return [false, false]; + }; + + const packetEvent = events.find((event: any) => { + if (event.type !== "recv_packet") { + return false; + } + const sourcePortAttr = event.attributes.find((attr: { key: string }) => { + return compareStringWithBase64OrPlain(attr.key, "packet_src_port")[0]; + }); + if (!sourcePortAttr) { + return false; + } + const sourceChannelAttr = event.attributes.find( + (attr: { key: string }) => { + return compareStringWithBase64OrPlain( + attr.key, + "packet_src_channel" + )[0]; + } + ); + if (!sourceChannelAttr) { + return false; + } + let isBase64 = false; + const sequenceAttr = event.attributes.find((attr: { key: string }) => { + const c = compareStringWithBase64OrPlain(attr.key, "packet_sequence"); + isBase64 = c[1]; + return c[0]; + }); + if (!sequenceAttr) { + return false; + } + + if (isBase64) { + return ( + Buffer.from(sourcePortAttr.value, "base64").toString() === + sourcePortId && + Buffer.from(sourceChannelAttr.value, "base64").toString() === + sourceChannelId && + Buffer.from(sequenceAttr.value, "base64").toString() === sequence + ); + } else { + return ( + sourcePortAttr.value === sourcePortId && + sourceChannelAttr.value === sourceChannelId && + sequenceAttr.value === sequence + ); + } + }); + if (!packetEvent) { + return -1; + } - return index; + return events.indexOf(packetEvent); } protected getIBCPacketSequenceFromTx( diff --git a/packages/background/src/recent-send-history/types.ts b/packages/background/src/recent-send-history/types.ts index 149dc931d7..c3b65ccf39 100644 --- a/packages/background/src/recent-send-history/types.ts +++ b/packages/background/src/recent-send-history/types.ts @@ -1,3 +1,5 @@ +import { AppCurrency } from "@keplr-wallet/types"; + export interface RecentSendHistory { timestamp: number; sender: string; @@ -46,7 +48,16 @@ export type IBCHistory = { completed: boolean; error?: string; rewound?: boolean; + // swap 이후에는 rewind가 불가능하기 때문에 + // swap 등에서는 이 값이 true일 수 있음 + rewoundButNextRewindingBlocked?: boolean; }[]; + + // Already notified to user + notified?: boolean; + notificationInfo?: { + currencies: AppCurrency[]; + }; } & (IBCTransferHistory | IBCSwapHistory); export interface IBCTransferHistory { @@ -67,4 +78,12 @@ export interface IBCSwapHistory { amount: string; denom: string; }[][]; + + swapRefundInfo?: { + chainId: string; + amount: { + amount: string; + denom: string; + }[]; + }; } diff --git a/packages/background/src/secret-wasm/service.ts b/packages/background/src/secret-wasm/service.ts index 84e2ea21ef..51681a5404 100644 --- a/packages/background/src/secret-wasm/service.ts +++ b/packages/background/src/secret-wasm/service.ts @@ -139,6 +139,7 @@ export class SecretWasmService { key: { readonly bech32Address: string; readonly isNanoLedger: boolean; + readonly isKeystone: boolean; } ): Promise { const cacheKey = `seed-${ @@ -151,7 +152,7 @@ export class SecretWasmService { } const seed = await (async () => { - if (key.isNanoLedger) { + if (key.isNanoLedger || key.isKeystone) { const arr = new Uint8Array(32); crypto.getRandomValues(arr); return arr; diff --git a/packages/background/src/tx/service.ts b/packages/background/src/tx/service.ts index d64a40e8cf..02355e82c8 100644 --- a/packages/background/src/tx/service.ts +++ b/packages/background/src/tx/service.ts @@ -3,6 +3,7 @@ import { TendermintTxTracer } from "@keplr-wallet/cosmos"; import { Notification } from "./types"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; import { Buffer } from "buffer/"; +import { retry } from "@keplr-wallet/common"; interface CosmosSdkError { codespace: string; @@ -90,21 +91,52 @@ export class BackgroundTxService { const txHash = Buffer.from(txResponse.txhash, "hex"); - const txTracer = new TendermintTxTracer(chainInfo.rpc, "/websocket"); - txTracer.traceTx(txHash).then((tx) => { - txTracer.close(); - - if (options.onFulfill) { - options.onFulfill(tx); - } - - if (!options.silent) { - BackgroundTxService.processTxResultNotification( - this.notification, - tx - ); + // 이 기능은 tx commit일때 notification을 띄울 뿐이다. + // 실제 로직 처리와는 관계가 없어야하기 때문에 여기서 await을 하면 안된다!! + retry( + () => { + return new Promise((resolve, reject) => { + const txTracer = new TendermintTxTracer( + chainInfo.rpc, + "/websocket" + ); + txTracer.addEventListener("close", () => { + // reject if ws closed before fulfilled + // 하지만 로직상 fulfill 되기 전에 ws가 닫히는게 되기 때문에 + // delay를 좀 준다. + // trace 이후 로직은 동기적인 로직밖에 없기 때문에 문제될 게 없다. + // 문제될게 없다. + setTimeout(() => { + reject(); + }, 500); + }); + txTracer.addEventListener("error", () => { + reject(); + }); + txTracer.traceTx(txHash).then((tx) => { + txTracer.close(); + + if (options.onFulfill) { + options.onFulfill(tx); + } + + if (!options.silent) { + BackgroundTxService.processTxResultNotification( + this.notification, + tx + ); + } + + resolve(); + }); + }); + }, + { + maxRetries: 10, + waitMsAfterError: 10 * 1000, // 10sec + maxWaitMsAfterError: 5 * 60 * 1000, // 5min } - }); + ); return txHash; } catch (e) { diff --git a/packages/chain-validator/package.json b/packages/chain-validator/package.json index 1c2e0f0534..0bab32502e 100644 --- a/packages/chain-validator/package.json +++ b/packages/chain-validator/package.json @@ -1,6 +1,6 @@ { "name": "@keplr-wallet/chain-validator", - "version": "0.12.32", + "version": "0.12.47", "main": "build/index.js", "author": "chainapsis", "license": "Apache-2.0", @@ -16,9 +16,9 @@ "lint-fix": "eslint --fix \"src/**/*\" && prettier --write \"src/**/*\"" }, "dependencies": { - "@keplr-wallet/cosmos": "0.12.32", - "@keplr-wallet/simple-fetch": "0.12.32", - "@keplr-wallet/types": "0.12.32", + "@keplr-wallet/cosmos": "0.12.47", + "@keplr-wallet/simple-fetch": "0.12.47", + "@keplr-wallet/types": "0.12.47", "joi": "^17.5.0", "utility-types": "^3.10.0" } diff --git a/packages/chain-validator/src/feature.ts b/packages/chain-validator/src/feature.ts index 5ba7ca2971..58331010f7 100644 --- a/packages/chain-validator/src/feature.ts +++ b/packages/chain-validator/src/feature.ts @@ -21,6 +21,7 @@ export const SupportedChainFeatures = [ "ibc-go-v7-hot-fix", "ibc-pfm", "authz-msg-revoke-fixed", + "osmosis-base-fee-beta", ]; /** diff --git a/packages/common/package.json b/packages/common/package.json index 47aaef1ac3..dd9386fb04 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@keplr-wallet/common", - "version": "0.12.32", + "version": "0.12.47", "main": "build/index.js", "author": "chainapsis", "license": "Apache-2.0", @@ -16,8 +16,8 @@ "lint-fix": "eslint --fix \"src/**/*\" && prettier --write \"src/**/*\"" }, "dependencies": { - "@keplr-wallet/crypto": "0.12.32", - "@keplr-wallet/types": "0.12.32", + "@keplr-wallet/crypto": "0.12.47", + "@keplr-wallet/types": "0.12.47", "buffer": "^6.0.3", "delay": "^4.4.0" }, diff --git a/packages/common/src/kv-store/base.ts b/packages/common/src/kv-store/base.ts index 92d487570f..5a49b8625c 100644 --- a/packages/common/src/kv-store/base.ts +++ b/packages/common/src/kv-store/base.ts @@ -9,7 +9,7 @@ export class BaseKVStore implements KVStore { async get(key: string): Promise { const k = this.prefix() + "/" + key; - const data = await this.provider.get(); + const data = await this.provider.get(k); return data[k]; } diff --git a/packages/common/src/kv-store/interface.ts b/packages/common/src/kv-store/interface.ts index 2450f2a7fe..334b8bb50a 100644 --- a/packages/common/src/kv-store/interface.ts +++ b/packages/common/src/kv-store/interface.ts @@ -9,6 +9,6 @@ export interface MultiGet { } export interface KVStoreProvider { - get(): Promise<{ [key: string]: any }>; + get(key: string): Promise<{ [key: string]: any }>; set(items: { [key: string]: any }): Promise; } diff --git a/packages/common/src/kv-store/memory.ts b/packages/common/src/kv-store/memory.ts index f8d1a2ec55..40667ebc04 100644 --- a/packages/common/src/kv-store/memory.ts +++ b/packages/common/src/kv-store/memory.ts @@ -4,8 +4,10 @@ import { KVStoreProvider } from "./interface"; class MemoryKVStoreProvider implements KVStoreProvider { private store: { [key: string]: any } = {}; - get() { - return Promise.resolve(this.store); + get(key: string) { + return Promise.resolve({ + [key]: this.store[key], + }); } set(items: { [key: string]: any }) { diff --git a/packages/cosmjs-test/package.json b/packages/cosmjs-test/package.json index 89ba0d7d59..b1ce001530 100644 --- a/packages/cosmjs-test/package.json +++ b/packages/cosmjs-test/package.json @@ -1,6 +1,6 @@ { "name": "cosmjs-test", - "version": "0.12.32", + "version": "0.12.47", "author": "chainapsis", "license": "Apache-2.0", "private": true, @@ -12,7 +12,7 @@ }, "devDependencies": { "@cosmjs/stargate": "^0.29.3", - "@keplr-wallet/provider-mock": "0.12.32", + "@keplr-wallet/provider-mock": "0.12.47", "secretjs": "^1.6.0" } } diff --git a/packages/cosmos/package.json b/packages/cosmos/package.json index 0286c13a4c..3487e5df01 100644 --- a/packages/cosmos/package.json +++ b/packages/cosmos/package.json @@ -1,6 +1,6 @@ { "name": "@keplr-wallet/cosmos", - "version": "0.12.32", + "version": "0.12.47", "main": "build/index.js", "author": "chainapsis", "license": "Apache-2.0", @@ -17,12 +17,12 @@ }, "dependencies": { "@ethersproject/address": "^5.6.0", - "@keplr-wallet/common": "0.12.32", - "@keplr-wallet/crypto": "0.12.32", - "@keplr-wallet/proto-types": "0.12.32", - "@keplr-wallet/simple-fetch": "0.12.32", - "@keplr-wallet/types": "0.12.32", - "@keplr-wallet/unit": "0.12.32", + "@keplr-wallet/common": "0.12.47", + "@keplr-wallet/crypto": "0.12.47", + "@keplr-wallet/proto-types": "0.12.47", + "@keplr-wallet/simple-fetch": "0.12.47", + "@keplr-wallet/types": "0.12.47", + "@keplr-wallet/unit": "0.12.47", "bech32": "^1.1.4", "buffer": "^6.0.3", "long": "^4.0.0", diff --git a/packages/cosmos/src/stargate/codec/index.ts b/packages/cosmos/src/stargate/codec/index.ts index 0b3a9785d6..5cbebe3bb0 100644 --- a/packages/cosmos/src/stargate/codec/index.ts +++ b/packages/cosmos/src/stargate/codec/index.ts @@ -12,6 +12,14 @@ import { MsgDelegate, MsgUndelegate, MsgBeginRedelegate, + MsgUnbondValidator, + MsgCancelUnbondingDelegation, + MsgTokenizeShares, + MsgRedeemTokensForShares, + MsgTransferTokenizeShareRecord, + MsgDisableTokenizeShares, + MsgEnableTokenizeShares, + MsgValidatorBond, } from "@keplr-wallet/proto-types/cosmos/staking/v1beta1/tx"; import { MsgExec, @@ -38,6 +46,23 @@ import { UnknownMessage } from "./unknown"; import { GenericAuthorization } from "@keplr-wallet/proto-types/cosmos/authz/v1beta1/authz"; import { StakeAuthorization } from "@keplr-wallet/proto-types/cosmos/staking/v1beta1/authz"; import { SendAuthorization } from "@keplr-wallet/proto-types/cosmos/bank/v1beta1/authz"; +import { + MsgLiquidStake, + MsgLSMLiquidStake, + MsgRedeemStake, + MsgRegisterHostZone, + MsgClaimUndelegatedTokens, + MsgRebalanceValidators, + MsgAddValidators, + MsgChangeValidatorWeight, + MsgDeleteValidator, + MsgRestoreInterchainAccount, + MsgUpdateValidatorSharesExchRate, + MsgCalibrateDelegation, + MsgClearBalance, + MsgUndelegateHost, + MsgUpdateInnerRedemptionRateBounds, +} from "@keplr-wallet/proto-types/stride/stakeibc/tx"; import { Buffer } from "buffer/"; export * from "./unknown"; @@ -202,6 +227,38 @@ defaultProtoCodec.registerAny( "/cosmos.staking.v1beta1.MsgBeginRedelegate", MsgBeginRedelegate ); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgUnbondValidator", + MsgUnbondValidator +); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgCancelUnbondingDelegation", + MsgCancelUnbondingDelegation +); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgTokenizeShares", + MsgTokenizeShares +); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgRedeemTokensForShares", + MsgRedeemTokensForShares +); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgTransferTokenizeShareRecord", + MsgTransferTokenizeShareRecord +); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgDisableTokenizeShares", + MsgDisableTokenizeShares +); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgEnableTokenizeShares", + MsgEnableTokenizeShares +); +defaultProtoCodec.registerAny( + "/cosmos.staking.v1beta1.MsgValidatorBond", + MsgValidatorBond +); defaultProtoCodec.registerAny( "/cosmwasm.wasm.v1.MsgExecuteContract", MsgExecuteContract @@ -256,3 +313,65 @@ defaultProtoCodec.registerAny( // ----- Authz grants ----- defaultProtoCodec.registerAny("/cosmos.authz.v1beta1.MsgRevoke", MsgRevoke); defaultProtoCodec.registerAny("/cosmos.authz.v1beta1.MsgExec", MsgExec); + +// Stride +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgLiquidStake", + MsgLiquidStake +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgLSMLiquidStake", + MsgLSMLiquidStake +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgRedeemStake", + MsgRedeemStake +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgRegisterHostZone", + MsgRegisterHostZone +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgClaimUndelegatedTokens", + MsgClaimUndelegatedTokens +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgRebalanceValidators", + MsgRebalanceValidators +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgAddValidators", + MsgAddValidators +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgChangeValidatorWeight", + MsgChangeValidatorWeight +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgDeleteValidator", + MsgDeleteValidator +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgRestoreInterchainAccount", + MsgRestoreInterchainAccount +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgUpdateValidatorSharesExchRate", + MsgUpdateValidatorSharesExchRate +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgCalibrateDelegation", + MsgCalibrateDelegation +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgClearBalance", + MsgClearBalance +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgUndelegateHost", + MsgUndelegateHost +); +defaultProtoCodec.registerAny( + "/stride.stakeibc.MsgUpdateInnerRedemptionRateBounds", + MsgUpdateInnerRedemptionRateBounds +); diff --git a/packages/cosmos/src/tx-tracer/index.ts b/packages/cosmos/src/tx-tracer/index.ts index 7f27261cbf..21ab5fcf20 100644 --- a/packages/cosmos/src/tx-tracer/index.ts +++ b/packages/cosmos/src/tx-tracer/index.ts @@ -236,6 +236,7 @@ export class TendermintTxTracer { traceTx( query: Uint8Array | Record ): Promise { + let resolved = false; return new Promise((resolve) => { // At first, try to query the tx at the same time of subscribing the tx. // But, the querying's error will be ignored. @@ -255,8 +256,41 @@ export class TendermintTxTracer { // noop }); + (async () => { + // We don't know why yet. For some unknown reason, there is a problem where Tendermint does not give value through subscribe forever. + // For now, as a simple solution, send tx_search periodically as well. + while (true) { + if ( + resolved || + this.readyState === WsReadyState.CLOSED || + this.readyState === WsReadyState.CLOSING + ) { + break; + } + + await new Promise((resolve) => setTimeout(resolve, 10000)); + + this.queryTx(query) + .then((result) => { + if (query instanceof Uint8Array) { + resolve(result); + return; + } + + if (result?.total_count !== "0") { + resolve(result); + return; + } + }) + .catch(() => { + // noop + }); + } + })(); + this.subscribeTx(query).then(resolve); }).then((tx) => { + resolved = true; // Occasionally, even if the subscribe tx event occurs, the state through query is not changed yet. // Perhaps it is because the block has not been committed yet even though the result of deliverTx in tendermint is complete. // This method is usually used to reflect the state change through query when tx is completed. diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 6bb057d9b7..f914e9b487 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,6 +1,6 @@ { "name": "@keplr-wallet/crypto", - "version": "0.12.32", + "version": "0.12.47", "main": "build/index.js", "author": "chainapsis", "license": "Apache-2.0", diff --git a/packages/extension/.infisical.json b/packages/extension/.infisical.json new file mode 100644 index 0000000000..c9180546a2 --- /dev/null +++ b/packages/extension/.infisical.json @@ -0,0 +1,5 @@ +{ + "workspaceId": "655c4c9013c9b9239b375a45", + "defaultEnvironment": "dev", + "gitBranchToEnvironmentMapping": null +} diff --git a/packages/extension/package.json b/packages/extension/package.json index eb814a9282..0f7a3635dd 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,6 +1,6 @@ { "name": "@keplr-wallet/extension", - "version": "0.12.32", + "version": "0.12.47", "author": "chainapsis", "license": "Apache-2.0", "private": true, @@ -25,25 +25,25 @@ "@ethersproject/transactions": "^5.7.0", "@floating-ui/react": "^0.23.0", "@floating-ui/react-dom": "^1.3.0", - "@keplr-wallet/analytics": "0.12.32", - "@keplr-wallet/background": "0.12.32", - "@keplr-wallet/chain-validator": "0.12.32", - "@keplr-wallet/common": "0.12.32", - "@keplr-wallet/cosmos": "0.12.32", - "@keplr-wallet/crypto": "0.12.32", - "@keplr-wallet/hooks": "0.12.32", - "@keplr-wallet/ledger-cosmos": "0.12.32", - "@keplr-wallet/popup": "0.12.32", - "@keplr-wallet/proto-types": "0.12.32", - "@keplr-wallet/provider": "0.12.32", - "@keplr-wallet/router": "0.12.32", - "@keplr-wallet/router-extension": "0.12.32", - "@keplr-wallet/simple-fetch": "0.12.32", - "@keplr-wallet/stores": "0.12.32", - "@keplr-wallet/stores-core": "0.12.32", - "@keplr-wallet/stores-etc": "0.12.32", - "@keplr-wallet/types": "0.12.32", - "@keplr-wallet/unit": "0.12.32", + "@keplr-wallet/analytics": "0.12.47", + "@keplr-wallet/background": "0.12.47", + "@keplr-wallet/chain-validator": "0.12.47", + "@keplr-wallet/common": "0.12.47", + "@keplr-wallet/cosmos": "0.12.47", + "@keplr-wallet/crypto": "0.12.47", + "@keplr-wallet/hooks": "0.12.47", + "@keplr-wallet/ledger-cosmos": "0.12.47", + "@keplr-wallet/popup": "0.12.47", + "@keplr-wallet/proto-types": "0.12.47", + "@keplr-wallet/provider": "0.12.47", + "@keplr-wallet/router": "0.12.47", + "@keplr-wallet/router-extension": "0.12.47", + "@keplr-wallet/simple-fetch": "0.12.47", + "@keplr-wallet/stores": "0.12.47", + "@keplr-wallet/stores-core": "0.12.47", + "@keplr-wallet/stores-etc": "0.12.47", + "@keplr-wallet/types": "0.12.47", + "@keplr-wallet/unit": "0.12.47", "@keystonehq/animated-qr": "^0.8.6", "@keystonehq/keystone-sdk": "^0.2.3", "@ledgerhq/devices": "^6.20.0", diff --git a/packages/extension/src/background/background.ts b/packages/extension/src/background/background.ts index f92fac7905..1e4f525e57 100644 --- a/packages/extension/src/background/background.ts +++ b/packages/extension/src/background/background.ts @@ -16,6 +16,7 @@ import { ExtensionKVStore } from "@keplr-wallet/common"; import { init } from "@keplr-wallet/background"; import scrypt from "scrypt-js"; import { Buffer } from "buffer/"; +import { Bech32Address } from "@keplr-wallet/cosmos"; import { CommunityChainInfoRepo, @@ -89,65 +90,253 @@ const { initFn, keyRingService } = init( return legacy.disabledChains ?? []; }, }, - async (service) => { - const kvStore = new ExtensionKVStore("store_chains_service_after_init"); - const celestiaTestnetMochaAdded = await kvStore.get( - "__celestia_testnet_mocha_added_v0.12.21" - ); - try { - if (!celestiaTestnetMochaAdded) { - await service.addSuggestedChainInfo({ - rpc: "https://rpc-celestia-testnet-mocha.keplr.app", - rest: "https://lcd-celestia-testnet-mocha.keplr.app", - chainId: "mocha-3", - chainName: "Celestia Mocha Testnet", - chainSymbolImageUrl: - "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/mocha/chain.png", - stakeCurrency: { - coinDenom: "TIA", - coinMinimalDenom: "utia", - coinDecimals: 6, - }, - bip44: { - coinType: 118, - }, - bech32Config: { - bech32PrefixAccAddr: "celestia", - bech32PrefixAccPub: "celestiapub", - bech32PrefixValAddr: "celestiavaloper", - bech32PrefixValPub: "celestiavaloperpub", - bech32PrefixConsAddr: "celestiavalcons", - bech32PrefixConsPub: "celestiavalconspub", - }, - currencies: [ - { - coinDenom: "TIA", - coinMinimalDenom: "utia", - coinDecimals: 6, + async (chainsService, lastEmbedChainInfos) => { + if (lastEmbedChainInfos.find((c) => c.chainId === "ixo-4")) { + await chainsService.addSuggestedChainInfo({ + rpc: "https://rpc-ixo.keplr.app", + rest: "https://lcd-ixo.keplr.app", + chainId: "ixo-4", + chainName: "ixo", + stakeCurrency: { + coinDenom: "IXO", + coinMinimalDenom: "uixo", + coinDecimals: 6, + }, + walletUrl: + process.env.NODE_ENV === "production" + ? "https://wallet.keplr.app/chains/ixo" + : "http://localhost:8080/chains/ixo", + walletUrlForStaking: + process.env.NODE_ENV === "production" + ? "https://wallet.keplr.app/chains/ixo" + : "http://localhost:8080/chains/ixo", + bip44: { + coinType: 118, + }, + bech32Config: Bech32Address.defaultBech32Config("ixo"), + currencies: [ + { + coinDenom: "IXO", + coinMinimalDenom: "uixo", + coinDecimals: 6, + }, + ], + feeCurrencies: [ + { + coinDenom: "IXO", + coinMinimalDenom: "uixo", + coinDecimals: 6, + }, + ], + features: ["ibc-transfer"], + }); + await chainsService.addSuggestedChainInfo({ + rpc: "https://rpc-iov.keplr.app", + rest: "https://lcd-iov.keplr.app", + chainId: "iov-mainnet-ibc", + chainName: "Starname", + stakeCurrency: { + coinDenom: "IOV", + coinMinimalDenom: "uiov", + coinDecimals: 6, + coinGeckoId: "starname", + }, + walletUrl: + process.env.NODE_ENV === "production" + ? "https://wallet.keplr.app/chains/starname" + : "http://localhost:8080/chains/starname", + walletUrlForStaking: + process.env.NODE_ENV === "production" + ? "https://wallet.keplr.app/chains/starname" + : "http://localhost:8080/chains/starname", + bip44: { + coinType: 234, + }, + bech32Config: Bech32Address.defaultBech32Config("star"), + currencies: [ + { + coinDenom: "IOV", + coinMinimalDenom: "uiov", + coinDecimals: 6, + coinGeckoId: "starname", + }, + ], + feeCurrencies: [ + { + coinDenom: "IOV", + coinMinimalDenom: "uiov", + coinDecimals: 6, + coinGeckoId: "starname", + gasPriceStep: { + low: 1, + average: 2, + high: 3, }, - ], - feeCurrencies: [ - { - coinDenom: "TIA", - coinMinimalDenom: "utia", - coinDecimals: 6, - gasPriceStep: { - low: 0.1, - average: 0.25, - high: 0.4, - }, + }, + ], + features: ["ibc-transfer"], + }); + await chainsService.addSuggestedChainInfo({ + rpc: "https://rpc-emoney.keplr.app", + rest: "https://lcd-emoney.keplr.app", + chainId: "emoney-3", + chainName: "e-Money", + stakeCurrency: { + coinDenom: "NGM", + coinMinimalDenom: "ungm", + coinDecimals: 6, + coinGeckoId: "e-money", + }, + walletUrl: + process.env.NODE_ENV === "production" + ? "https://wallet.keplr.app/chains/e-money" + : "http://localhost:8080/chains/e-money", + walletUrlForStaking: + process.env.NODE_ENV === "production" + ? "https://wallet.keplr.app/chains/e-money" + : "http://localhost:8080/chains/e-money", + bip44: { + coinType: 118, + }, + bech32Config: Bech32Address.defaultBech32Config("emoney"), + currencies: [ + { + coinDenom: "NGM", + coinMinimalDenom: "ungm", + coinDecimals: 6, + coinGeckoId: "e-money", + }, + { + coinDenom: "EEUR", + coinMinimalDenom: "eeur", + coinDecimals: 6, + coinGeckoId: "e-money-eur", + }, + { + coinDenom: "EDKK", + coinMinimalDenom: "edkk", + coinDecimals: 6, + }, + { + coinDenom: "ESEK", + coinMinimalDenom: "esek", + coinDecimals: 6, + }, + { + coinDenom: "ENOK", + coinMinimalDenom: "enok", + coinDecimals: 6, + }, + { + coinDenom: "ECHF", + coinMinimalDenom: "echf", + coinDecimals: 6, + }, + ], + feeCurrencies: [ + { + coinDenom: "NGM", + coinMinimalDenom: "ungm", + coinDecimals: 6, + coinGeckoId: "e-money", + gasPriceStep: { + low: 1, + average: 1, + high: 1, }, - ], - features: [], - }); - } - } catch (e) { - console.log(e); - // Ignore error + }, + { + coinDenom: "EEUR", + coinMinimalDenom: "eeur", + coinDecimals: 6, + coinGeckoId: "e-money-eur", + gasPriceStep: { + low: 1, + average: 1, + high: 1, + }, + }, + { + coinDenom: "ECHF", + coinMinimalDenom: "echf", + coinDecimals: 6, + gasPriceStep: { + low: 1, + average: 1, + high: 1, + }, + }, + { + coinDenom: "ESEK", + coinMinimalDenom: "esek", + coinDecimals: 6, + gasPriceStep: { + low: 1, + average: 1, + high: 1, + }, + }, + { + coinDenom: "ENOK", + coinMinimalDenom: "enok", + coinDecimals: 6, + gasPriceStep: { + low: 1, + average: 1, + high: 1, + }, + }, + { + coinDenom: "EDKK", + coinMinimalDenom: "edkk", + coinDecimals: 6, + gasPriceStep: { + low: 1, + average: 1, + high: 1, + }, + }, + ], + features: ["ibc-transfer"], + }); } - if (!celestiaTestnetMochaAdded) { - await kvStore.set("__celestia_testnet_mocha_added_v0.12.21", true); + if (lastEmbedChainInfos.find((c) => c.chainId === "tgrade-mainnet-1")) { + await chainsService.addSuggestedChainInfo({ + rpc: "https://rpc-tgrade.keplr.app", + rest: "https://lcd-tgrade.keplr.app", + chainId: "tgrade-mainnet-1", + chainName: "Tgrade", + stakeCurrency: { + coinDenom: "TGD", + coinMinimalDenom: "utgd", + coinDecimals: 6, + }, + bip44: { + coinType: 118, + }, + bech32Config: Bech32Address.defaultBech32Config("tgrade"), + currencies: [ + { + coinDenom: "TGD", + coinMinimalDenom: "utgd", + coinDecimals: 6, + }, + ], + feeCurrencies: [ + { + coinDenom: "TGD", + coinMinimalDenom: "utgd", + coinDecimals: 6, + gasPriceStep: { + low: 0.05, + average: 0.05, + high: 0.075, + }, + }, + ], + features: ["cosmwasm", "ibc-transfer", "ibc-go", "wasmd_0.24+"], + }); } } ); diff --git a/packages/extension/src/bottom-tabs.tsx b/packages/extension/src/bottom-tabs.tsx index 8e908fb708..f670832f9a 100644 --- a/packages/extension/src/bottom-tabs.tsx +++ b/packages/extension/src/bottom-tabs.tsx @@ -2,37 +2,7 @@ import React, { FunctionComponent, PropsWithChildren } from "react"; import { GlobalSimpleBarProvider } from "./hooks/global-simplebar"; import { Link, useLocation } from "react-router-dom"; import { ColorPalette } from "./styles"; -import { createGlobalStyle, useTheme } from "styled-components"; -import { Tooltip } from "./components/tooltip"; -import { Box } from "./components/box"; -import { Button } from "./components/button"; -import { YAxis } from "./components/axis"; -import { Body2, Subtitle3 } from "./components/typography"; -import { Gutter } from "./components/gutter"; -import { observer } from "mobx-react-lite"; -import { useStore } from "./stores"; -import { useNavigate } from "react-router"; -import { FormattedMessage, useIntl } from "react-intl"; - -// ibc swap을 써보라는 tooltip에 대해서 animation을 넣고싶은데 -// 이거하려고 react-spring을 쓰긴 빡세고 -// css로 하려는데 tooltip component 내부에 넣기도 빡세고 해서 -// 그냥 global css로 처리한다. -const BottomTabsGlobalStyleTransitions = createGlobalStyle` - .__bottom_tabs__ibc_swap__floating-ui__tooltip__content__fadeInUp-enter { - animation: __bottom_tabs__ibc_swap__floating-ui__tooltip__content__fadeInUp 0.6s cubic-bezier(0.33, 1, 0.68, 1); - } - @keyframes __bottom_tabs__ibc_swap__floating-ui__tooltip__content__fadeInUp { - 0%{ - transform:translate(0, 100%); - opacity: 0; - } - 100%{ - transform:translate(0, 0); - opacity: 1; - } - } -`; +import { useTheme } from "styled-components"; export const BottomTabsHeightRem = "3.75rem"; @@ -47,16 +17,11 @@ export const BottomTabsRouteProvider: FunctionComponent< forceHideBottomTabs?: boolean; }> -> = observer(({ children, isNotReady, tabs, forceHideBottomTabs }) => { - const { uiConfigStore } = useStore(); - +> = ({ children, isNotReady, tabs, forceHideBottomTabs }) => { const location = useLocation(); - const navigate = useNavigate(); const theme = useTheme(); - const intl = useIntl(); - const shouldBottomTabsShown = !forceHideBottomTabs && tabs.find((tab) => tab.pathname === location.pathname); @@ -79,7 +44,6 @@ export const BottomTabsRouteProvider: FunctionComponent< {children} - {shouldBottomTabsShown ? (
- - - - - - - - - - -
) : null} ); -}); +}; export const BottomTabHomeIcon: FunctionComponent<{ width: string; diff --git a/packages/extension/src/components/collapsible-list/collapsible-list.tsx b/packages/extension/src/components/collapsible-list/collapsible-list.tsx index d0565093a6..4b2a5d647c 100644 --- a/packages/extension/src/components/collapsible-list/collapsible-list.tsx +++ b/packages/extension/src/components/collapsible-list/collapsible-list.tsx @@ -35,6 +35,7 @@ export const CollapsibleList: FunctionComponent = ({ title, items, lenAlwaysShown, + hideNumInTitle, }) => { if (!lenAlwaysShown || lenAlwaysShown < 0) { lenAlwaysShown = items.length; @@ -66,7 +67,7 @@ export const CollapsibleList: FunctionComponent = ({ : ColorPalette["gray-50"], }} > - {items.length} + {hideNumInTitle ? "**" : items.length} diff --git a/packages/extension/src/components/collapsible-list/types.ts b/packages/extension/src/components/collapsible-list/types.ts index 6d1ba02d96..3c4517219c 100644 --- a/packages/extension/src/components/collapsible-list/types.ts +++ b/packages/extension/src/components/collapsible-list/types.ts @@ -5,4 +5,7 @@ export interface CollapsibleListProps { items: React.ReactNode[]; lenAlwaysShown?: number; + + // privacy mode를 위해서 대충 추가됨 + hideNumInTitle?: boolean; } diff --git a/packages/extension/src/components/contract-item/index.tsx b/packages/extension/src/components/contract-item/index.tsx index ba76e640cb..811e266b0d 100644 --- a/packages/extension/src/components/contract-item/index.tsx +++ b/packages/extension/src/components/contract-item/index.tsx @@ -6,7 +6,7 @@ import { Gutter } from "../gutter"; import { YAxis } from "../axis"; import { Caption1, Subtitle3 } from "../typography"; import { Column, Columns } from "../column"; -import { ChainImageFallback } from "../image"; +import { RawImageFallback } from "../image"; import { TextButton } from "../button-text"; import { useTheme } from "styled-components"; import { useIntl } from "react-intl"; @@ -42,14 +42,7 @@ export const ContractAddressItem: FunctionComponent<{ > - + = ({ + width = "1.5rem", + height = "1.5rem", + color, +}) => { + return ( + + + + + + ); +}; diff --git a/packages/extension/src/components/icon/eye.tsx b/packages/extension/src/components/icon/eye.tsx new file mode 100644 index 0000000000..144146f5bd --- /dev/null +++ b/packages/extension/src/components/icon/eye.tsx @@ -0,0 +1,30 @@ +import React, { FunctionComponent } from "react"; +import { IconProps } from "./types"; + +export const EyeIcon: FunctionComponent = ({ + width = "1.5rem", + height = "1.5rem", + color, +}) => { + return ( + + + + + ); +}; diff --git a/packages/extension/src/components/icon/index.tsx b/packages/extension/src/components/icon/index.tsx index f334942baa..daa3734a96 100644 --- a/packages/extension/src/components/icon/index.tsx +++ b/packages/extension/src/components/icon/index.tsx @@ -41,3 +41,5 @@ export * from "./x-mark"; export * from "./check-circle"; export * from "./home"; export * from "./qr-code"; +export * from "./eye"; +export * from "./eye-slash"; diff --git a/packages/extension/src/components/image/index.tsx b/packages/extension/src/components/image/index.tsx index 121079f0f8..9dd74199ce 100644 --- a/packages/extension/src/components/image/index.tsx +++ b/packages/extension/src/components/image/index.tsx @@ -1,4 +1,7 @@ import React, { FunctionComponent, useLayoutEffect, useState } from "react"; +import { AppCurrency, ChainInfo } from "@keplr-wallet/types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../stores"; /** * 그냥 이미지 컴포넌트인데 오류 났을때 대체 이미지를 보여주는 기능이 있음 @@ -41,13 +44,15 @@ export const Image: FunctionComponent< ); }; -export const ChainImageFallback: FunctionComponent< +export const RawImageFallback: FunctionComponent< React.ImgHTMLAttributes & { src: string | undefined; // 얘는 undefined더라도 일단 넣으라고 일부로 ?를 안붙인거임. alt: string; + + size: string; } > = (props) => { - const { style, ...otherProps } = props; + const { style, size, ...otherProps } = props; return ( ); }; + +export const ChainImageFallback: FunctionComponent< + Omit, "src" | "alt"> & { + chainInfo: ChainInfo; + + size: string; + alt?: string; + } +> = (props) => { + const { style, size, chainInfo, ...otherProps } = props; + + return ( +
+ {chainInfo.chainName} +
+ ); +}; + +export const CurrencyImageFallback: FunctionComponent< + Omit, "src" | "alt"> & { + chainInfo: ChainInfo; + currency: AppCurrency; + + size: string; + alt?: string; + } +> = observer((props) => { + const { chainStore } = useStore(); + + const { style, size, currency, chainInfo, ...otherProps } = props; + + return ( +
+ {currency.coinDenom} +
+ {(() => { + let isAxelarBridged = false; + const axelarChainIdentifier = "axelar-dojo"; + + if ("paths" in currency) { + if ( + "originChainId" in currency && + currency.originChainId && + "originCurrency" in currency && + currency.originCurrency + ) { + if ( + chainStore.hasChain(currency.originChainId) && + chainStore.getChain(currency.originChainId).chainIdentifier === + axelarChainIdentifier && + currency.originCurrency.coinMinimalDenom !== "uaxl" + ) { + isAxelarBridged = true; + } + } + } else { + if ( + chainStore.getChain(chainInfo.chainId).chainIdentifier === + axelarChainIdentifier && + currency.coinMinimalDenom !== "uaxl" + ) { + isAxelarBridged = true; + } + } + + if (isAxelarBridged && chainStore.hasChain(axelarChainIdentifier)) { + const axlCurrency = chainStore + .getChain(axelarChainIdentifier) + .findCurrency("uaxl"); + + if (axlCurrency && axlCurrency.coinImageUrl) { + return ( + axelar bridged token + ); + } + } + })()} +
+
+ ); +}); diff --git a/packages/extension/src/components/tooltip/index.tsx b/packages/extension/src/components/tooltip/index.tsx index c47467c06c..b08ea3438f 100644 --- a/packages/extension/src/components/tooltip/index.tsx +++ b/packages/extension/src/components/tooltip/index.tsx @@ -26,7 +26,8 @@ export const Tooltip: FunctionComponent< allowedPlacements?: ("top" | "bottom" | "left" | "right")[]; - contentClassName?: string; + forceWidth?: string; + backgroundColor?: string; hideBorder?: boolean; borderColor?: string; @@ -37,7 +38,6 @@ export const Tooltip: FunctionComponent< content, isAlwaysOpen = false, allowedPlacements, - contentClassName, backgroundColor: propBackgroundColor, hideBorder, borderColor: propBorderColor, @@ -111,7 +111,6 @@ export const Tooltip: FunctionComponent< {content && (isAlwaysOpen || ((enabled == null || enabled) && isOpen)) ? (
{ "store_queries/", "store_prices/", "store_ibc_curreny_registrar/", + "store_lsm_currency_registrar/", "store_gravity_bridge_currency_registrar/", "store_axelar_evm_bridge_currency_registrar/", ]; diff --git a/packages/extension/src/index.tsx b/packages/extension/src/index.tsx index 112590f933..131df807b1 100644 --- a/packages/extension/src/index.tsx +++ b/packages/extension/src/index.tsx @@ -129,6 +129,7 @@ const RoutesAfterReady: FunctionComponent = observer(() => { accountStore, keyRingStore, ibcCurrencyRegistrar, + lsmCurrencyRegistrar, ibcChannelStore, gravityBridgeCurrencyRegistrar, axelarEVMBridgeCurrencyRegistrar, @@ -198,6 +199,10 @@ const RoutesAfterReady: FunctionComponent = observer(() => { return false; } + if (!lsmCurrencyRegistrar.isInitialized) { + return false; + } + if (!priceStore.isInitialized) { return false; } @@ -228,6 +233,7 @@ const RoutesAfterReady: FunctionComponent = observer(() => { chainStore.chainInfos, isURLUnlockPage, ibcCurrencyRegistrar.isInitialized, + lsmCurrencyRegistrar.isInitialized, priceStore.isInitialized, uiConfigStore.isInitialized, uiConfigStore.isDeveloper, diff --git a/packages/extension/src/languages/en.json b/packages/extension/src/languages/en.json index 065b12a69b..7146df5fc8 100644 --- a/packages/extension/src/languages/en.json +++ b/packages/extension/src/languages/en.json @@ -20,7 +20,7 @@ "error.not-enough-balance-to-pay-fee": "Not enough balance to pay fee", "error.claimable-reward-is-smaller-than-the-required-fee": "Your claimable reward is smaller than the required fee.", "error.outdated-cosmos-sdk": "Not supported: outdated version of cosmos-sdk", - "error.can-not-pay-for-fee-by-stake-currency": "Can't pay for fee by stake currency", + "error.can-not-find-fee-for-claim-all": "Can't find fee currency to pay", "pages.register.components.header.intro-title": "Your Interchain Gateway", "pages.register.components.header.header-step.title": "Step", @@ -202,8 +202,8 @@ "page.setting.general.theme-title": "Theme", "page.setting.general.manage-authz-title": "Manage AuthZ", "page.setting.general.link-kpelr-mobile-title": "Link Keplr Mobile", - "page.setting.general.manage-non-native-chains-title": "Manage Non-Native Chains", - "page.setting.general.manage-non-native-chains-paragraph": "Add or remove non-native chains operated by external parties", + "page.setting.general.manage-non-native-chains-title": "Add/Remove Non-Native Chains", + "page.setting.general.manage-non-native-chains-paragraph": "Non-native chains should be first added to Keplr to manage their visibility.", "page.setting.general.manage-chain-visibility-title": "Manage Chain Visibility", "page.setting.general.manage-chain-visibility-paragraph": "Select chains (and their assets) to be displayed on your current account", @@ -361,6 +361,7 @@ "page.ibc-swap.title.swap": "Swap", "page.ibc-swap.button.next": "Next", "page.ibc-swap.error.no-route-found": "No routes found for this swap", + "page.ibc-swap.warning.high-price-impact": "Due to a lack of liquidity, there is a significant difference in the USD value between the assets before and after the swap. Click \"Next\" if you still want to proceed.", "page.ibc-swap.components.swap-asset-info.from": "From", "page.ibc-swap.components.swap-asset-info.to": "To", "page.ibc-swap.components.swap-asset-info.max-asset": "Max: {asset}", @@ -369,7 +370,7 @@ "page.ibc-swap.components.swap-asset-info.modal.search.placeholder": "Search for a chain", "page.ibc-swap.components.swap-fee-info.button.transaction-fee": "Transaction Fee", "page.ibc-swap.components.swap-fee-info.button.service-fee": "Keplr Swap Fee", - "page.ibc-swap.components.swap-fee-info.button.service-fee.paragraph": "Launch Event: Swap with 0% Keplr fee for the first month", + "page.ibc-swap.components.swap-fee-info.button.service-fee.paragraph": "Launch Event: Swap with 0% Keplr fee until the end of the year 2023", "page.ibc-swap.components.slippage-modal.title": "Slippage Settings", "page.ibc-swap.components.slippage-modal.label.slippage-tolerance": "Slippage Tolerance", "page.ibc-swap.components.slippage-modal.label.slippage-custom": "Custom Slippage", @@ -420,6 +421,10 @@ "page.sign.components.messages.transfer.paragraph": "Send {coin} to {address} via {channelId}", "page.sign.components.messages.transfer.forwarding.close-button": "Close", "page.sign.components.messages.transfer.forwarding.open-button": "See forwarding data", + "page.sign.components.messages.pay-packet-fee.title": "Pay IBC Relayer Fee", + "page.sign.components.messages.pay-packet-fee.paragraph": "Spend {total} to pay fees for relaying IBC packet to {channelId}", + "page.sign.components.messages.pay-packet-fee.close-button": "Close", + "page.sign.components.messages.pay-packet-fee.open-button": "Details", "page.sign.components.messages.undelegate.title": "Undelegate", "page.sign.components.messages.undelegate.paragraph": "Undelegate {coin} from {from} Asset will be liquid after unbonding period", "page.sign.components.messages.vote.title": "Vote", @@ -499,6 +504,8 @@ "page.main.components.ibc-history-view.item.succeed": "IBC Transfer Successful", "page.main.components.ibc-history-view.ibc-swap.item.pending": "Swap in Progress", "page.main.components.ibc-history-view.ibc-swap.item.succeed": "Swap Successful", + "page.main.components.ibc-history-view.ibc-swap.item.refund.pending": "Refund in Progress", + "page.main.components.ibc-history-view.ibc-swap.item.refund.succeed": "Refund Completed", "page.main.components.ibc-history-view.paragraph": "Transfer {assets} from {sourceChain} to {destinationChain}", "page.main.components.ibc-history-view.ibc-swap.paragraph": "Swapping {assets} to {destinationDenom}", "page.main.components.ibc-history-view.ibc-swap.succeed.paragraph": "You received {assets}", @@ -510,6 +517,7 @@ "page.main.components.ibc-history-view.failed.complete": "Transfer failed. Refunding completed.", "page.main.components.ibc-history-view.ibc-swap.failed.in-progress": "Swap failed. Refunding in progress.", "page.main.components.ibc-history-view.ibc-swap.failed.complete": "Swap failed. Refunding completed.", + "page.main.components.ibc-history-view.ibc-swap.failed.after-swap.complete": "Swap succeeded, but failed to transfer to the destination chain. {assets} have been refunded to {chain}.", "page.main.components.ibc-transfer-view.title": "Advanced IBC Transfer", "page.main.components.ibc-transfer-view.tooltip": "Transfers might take longer if relayers are inactive. Ask in the corresponding community groups for help in reaching the relaying validators.", "page.main.components.ibc-transfer-view.paragraph": "Send tokens over IBC", @@ -533,7 +541,7 @@ "page.main.components.menu-bar.go-to-keplr-chain-registry": "Add More Chains", "page.main.components.deposit-modal.title": "Copy Address", "page.main.components.deposit-modal.search-placeholder": "Search for a chain", - "page.main.components.deposit-modal.empty-text": "To use an address on certain chain, you may need to first visit the \"Manage Chain Visibility\" in the side menu and make the chain visible on your wallet.", + "page.main.components.deposit-modal.empty-text": "To use an address on certain chain, you may need to first visit the \"Manage Chain Visibility\" in the side menu and make the chain visible on your wallet.", "page.main.components.buy-crypto-modal.title": "Buy Crypto", "page.main.components.looking-for-chains.title": "Looking for a chain?", "page.main.components.looking-for-chains.enable-button": "Manage", @@ -542,13 +550,13 @@ "page.main.components.token-found-modal.add-chains": "Add Chains", "page.main.components.token-item.copy-address.copied": "Copied", + "page.main.layouts.header.new-chain.title": "✨ {chains} Now Integrated!", + "page.main.layouts.header.new-chain.paragraph": "To show the chains and their assets on this account, visit \"Manage Chain Visibility\".", + "page.main.layouts.header.new-chain.button": "Got it!", + "page.ledger-grant.title": "Allow Browser to Connect to Ledger", "page.ledger-grant.paragraph": "You need to reapprove connection to your Ledger. Select the appropriate app, and after successfully connecting with your Ledger device, close this page and retry your previous transaction (signing).", - "new-feature.ibc-swap.title": "✨ Swap is Here!", - "new-feature.ibc-swap.paragraph": "Swap tokens between different chains with a simple click. Give it a try!", - "new-feature.ibc-swap.button": "Got it!", - "components.empty-view.text": "No {subject} Yet", "components.input.recipient-input.wallet-address-label": "Wallet Address or ICNS", "components.input.amount-input.amount-label": "Amount", diff --git a/packages/extension/src/languages/ko.json b/packages/extension/src/languages/ko.json index 1cf7f3c1c6..ca3cf35653 100644 --- a/packages/extension/src/languages/ko.json +++ b/packages/extension/src/languages/ko.json @@ -20,7 +20,7 @@ "error.not-enough-balance-to-pay-fee": "수수료를 지불할 잔액이 부족합니다.", "error.claimable-reward-is-smaller-than-the-required-fee": "받을 리워드가 지불할 수수료보다 적습니다.", "error.outdated-cosmos-sdk": "cosmos-sdk 버전이 오래되서 지원되지 않습니다.", - "error.can-not-pay-for-fee-by-stake-currency": "스테이킹 토큰으로는 수수료를 지불할 수 없습니다.", + "error.can-not-find-fee-for-claim-all": "수수료로 지불할 통화를 찾을 수 없습니다.", "pages.register.components.header.intro-title": "내 손 안의 인터체인", "pages.register.components.header.header-step.title": "단계", @@ -202,8 +202,8 @@ "page.setting.general.theme-title": "테마", "page.setting.general.manage-authz-title": "AuthZ 관리", "page.setting.general.link-kpelr-mobile-title": "케플러 모바일과 연결하기", - "page.setting.general.manage-non-native-chains-title": "선택형 체인 관리", - "page.setting.general.manage-non-native-chains-paragraph": "외부에서 관리되는 체인들 연결 추가/삭제하기", + "page.setting.general.manage-non-native-chains-title": "선택형 체인 추가 및 삭제", + "page.setting.general.manage-non-native-chains-paragraph": "선택형 체인들은 먼저 케플러 익스텐션에 추가해야 \"체인 표시\" 기능에서 사용할 수 있습니다.", "page.setting.general.manage-chain-visibility-title": "체인 표시", "page.setting.general.manage-chain-visibility-paragraph": "현재 계정에 표시될 체인(해당 체인의 자산 포함)을 선택해주세요.", @@ -356,6 +356,7 @@ "page.ibc-swap.title.swap": "토큰 교환", "page.ibc-swap.button.next": "다음", "page.ibc-swap.error.no-route-found": "이 교환에 맞는 경로가 검색되지 않습니다.", + "page.ibc-swap.warning.high-price-impact": "유동성 부족으로 인해 교환 전과 후 자산의 USD 가치가 상당한 차이를 보이고 있습니다. 그래도 계속 진행하시려면 \"다음\"을 클릭하세요.", "page.ibc-swap.components.swap-asset-info.from": "교환할 자산", "page.ibc-swap.components.swap-asset-info.to": "교환 후 받을 자산", "page.ibc-swap.components.swap-asset-info.max-asset": "최대: {asset}", @@ -364,7 +365,7 @@ "page.ibc-swap.components.swap-asset-info.modal.search.placeholder": "체인 검색", "page.ibc-swap.components.swap-fee-info.button.transaction-fee": "트랜잭션 수수료", "page.ibc-swap.components.swap-fee-info.button.service-fee": "케플러 수수료", - "page.ibc-swap.components.swap-fee-info.button.service-fee.paragraph": "런칭 이벤트: 첫달동안 케플러 서비스 수수료 0%로 운영됩니다.", + "page.ibc-swap.components.swap-fee-info.button.service-fee.paragraph": "런칭 이벤트: 2023년 말까지 케플러 서비스 수수료 0%로 운영됩니다.", "page.ibc-swap.components.slippage-modal.title": "슬리피지 설정", "page.ibc-swap.components.slippage-modal.label.slippage-tolerance": "슬리피지 허용치", "page.ibc-swap.components.slippage-modal.label.slippage-custom": "커스텀 설정", @@ -406,6 +407,10 @@ "page.sign.components.messages.transfer.paragraph": "채널 {channelId}를 통해 {address}에게 {coin}를 보냅니다.", "page.sign.components.messages.transfer.forwarding.close-button": "닫기", "page.sign.components.messages.transfer.forwarding.open-button": "포워딩 정보 보기", + "page.sign.components.messages.pay-packet-fee.title": "릴레이어에게 수수료 지불", + "page.sign.components.messages.pay-packet-fee.paragraph": "{channelId}로 가는 IBC 패킷에 {total}를 수수료로 지불합니다.", + "page.sign.components.messages.pay-packet-fee.close-button": "접기", + "page.sign.components.messages.pay-packet-fee.open-button": "상세보기", "page.sign.components.messages.undelegate.title": "위임 해제", "page.sign.components.messages.undelegate.paragraph": "{from}로부터 {coin}를 위임해제합니다.{br}단, 언본딩 기간이 끝나야 자산을 회수할 수 있습니다.", "page.sign.components.messages.vote.title": "투표", @@ -485,6 +490,8 @@ "page.main.components.ibc-history-view.item.succeed": "IBC 전송 성공", "page.main.components.ibc-history-view.ibc-swap.item.pending": "토큰 교환 진행중", "page.main.components.ibc-history-view.ibc-swap.item.succeed": "토큰 교환 성공", + "page.main.components.ibc-history-view.ibc-swap.item.refund.pending": "환불 진행중", + "page.main.components.ibc-history-view.ibc-swap.item.refund.succeed": "환불 완료", "page.main.components.ibc-history-view.paragraph": "{sourceChain}에서 {destinationChain}(으)로 {assets} 보내기", "page.main.components.ibc-history-view.ibc-swap.paragraph": "{assets}을 {destinationDenom}로 교환 중", "page.main.components.ibc-history-view.ibc-swap.succeed.paragraph": "{assets}을 받았습니다", @@ -496,6 +503,7 @@ "page.main.components.ibc-history-view.failed.complete": "전송 실패. 환불이 완료되었습니다", "page.main.components.ibc-history-view.ibc-swap.failed.in-progress": "교환에 실패했습니다. 토큰을 환불 중입니다", "page.main.components.ibc-history-view.ibc-swap.failed.complete": "토큰 교환 실패. 환불이 완료되었습니다", + "page.main.components.ibc-history-view.ibc-swap.failed.after-swap.complete": "교환에는 성공했으나 목적지로 전송에 실패하였습니다. {assets}가 마지막 경유지 {chain}로 반환되었습니다.", "page.main.components.ibc-transfer-view.title": "고급 IBC 전송", "page.main.components.ibc-transfer-view.tooltip": "릴레이어가 비활성 상태일 경우 전송이 더 오래 걸릴 수 있습니다. 이 경우 해당 커뮤니티를 방문하여 릴레이어 담당 벨리데이터에게 연락해야 합니다.", "page.main.components.ibc-transfer-view.paragraph": "토큰을 IBC를 통해 전송하세요", @@ -519,7 +527,7 @@ "page.main.components.menu-bar.go-to-keplr-chain-registry": "더 많은 체인 추가하기", "page.main.components.deposit-modal.title": "주소 복사", "page.main.components.deposit-modal.search-placeholder": "체인 검색", - "page.main.components.deposit-modal.empty-text": "특정 체인의 주소를 사용하기 위해선 먼저 사이드바 메뉴의 \"체인 표시\"를 방문하여 해당 체인을 지갑에 표시하도록 설정해야 합니다.", + "page.main.components.deposit-modal.empty-text": "특정 체인의 주소를 사용하기 위해선 먼저 사이드바 메뉴의 \"체인 표시\"를 방문하여 해당 체인을 지갑에 표시하도록 설정해야 합니다.", "page.main.components.buy-crypto-modal.title": "암호화폐 구매", "page.main.components.looking-for-chains.title": "체인을 찾으시나요?", "page.main.components.looking-for-chains.enable-button": "체인 표시하기", @@ -528,13 +536,13 @@ "page.main.components.token-found-modal.add-chains": "체인 추가", "page.main.components.token-item.copy-address.copied": "복사됨", + "page.main.layouts.header.new-chain.title": "{chains} 체인이 추가되었습니다!", + "page.main.layouts.header.new-chain.paragraph": "\"체인 표시\"에서 신규 체인을 활성화하면 이 계정에서 사용할 수 있습니다.", + "page.main.layouts.header.new-chain.button": "좋아요!", + "page.ledger-grant.title": "렛저와 브라우저 간의 연결을 재허용합니다", "page.ledger-grant.paragraph": "렛저의 연결 권한을 다시 허용해보는 절차입니다. 먼저, 렛저 장치에서 올바른 앱을 선택하여 연 뒤 장치의 연결을 다시 시도해보세요. 연결이 완료되면 기존 화면으로 돌아가 수행하려던 트랜잭션을 다시 시도해볼 수 있습니다.", - "new-feature.ibc-swap.title": "✨ 토큰 교환이 생겼어요!", - "new-feature.ibc-swap.paragraph": "체인 내 혹은 체인간 다양한 토큰을 서로 교환할 수 있어요. 한번 사용해보세요!", - "new-feature.ibc-swap.button": "좋아요!", - "components.empty-view.text": "{subject} 데이터가 없습니다.", "components.input.recipient-input.wallet-address-label": "지갑 주소 혹은 ICNS", "components.input.amount-input.amount-label": "수량", diff --git a/packages/extension/src/layouts/header/header.tsx b/packages/extension/src/layouts/header/header.tsx index bb0e4d5024..e528a37646 100644 --- a/packages/extension/src/layouts/header/header.tsx +++ b/packages/extension/src/layouts/header/header.tsx @@ -125,6 +125,19 @@ const Styles = { } }}; `, + BottomButtonMockBackplate: styled.div` + background: ${(props) => + props.theme.mode === "light" + ? ColorPalette["light-gradient"] + : ColorPalette["gray-700"]}; + + body[data-white-background="true"] && { + background: ${(props) => + props.theme.mode === "light" + ? ColorPalette["white"] + : ColorPalette["gray-700"]}; + } + `, }; export const HeaderLayout: FunctionComponent< @@ -223,6 +236,23 @@ export const HeaderLayout: FunctionComponent< bottom: additionalPaddingBottom || "0", }} > + {/* + scroll이 생겼을때 버튼 뒤로 UI가 보이는걸 대충 방지한다. + 버튼들이 border radius를 가지고 있는데 이부분만 남기고 나머지만 안보여줄 효과적인 방법은 없다. + 일단 border radius를 감안해서 약간 height를 적게 줘서 mock backplate를 만든다. + 나중에 UI 상에서 문제가 된다면 따로 prop등을 임시로 사용해서 조절해야한다... + */} + {bottomPadding !== "0" ? ( + + ) : null} {(() => { if (bottomButton.isSpecial) { // isSpecial is not used. diff --git a/packages/extension/src/manifest.v2.json b/packages/extension/src/manifest.v2.json index f4bf6d0656..a50f26d3af 100644 --- a/packages/extension/src/manifest.v2.json +++ b/packages/extension/src/manifest.v2.json @@ -3,7 +3,7 @@ "name": "Keplr", "description": "Keplr is a browser extension wallet for the Inter blockchain ecosystem.", - "version": "0.12.32", + "version": "0.12.47", "icons": { "16": "assets/icon-16.png", "48": "assets/icon-48.png", @@ -18,7 +18,14 @@ "page": "background.html", "persistent": true }, - "permissions": ["storage", "notifications", "identity", "idle", "alarms"], + "permissions": [ + "storage", + "notifications", + "identity", + "idle", + "alarms", + "unlimitedStorage" + ], "content_scripts": [ { "matches": [""], diff --git a/packages/extension/src/manifest.v3.json b/packages/extension/src/manifest.v3.json index dd4bb160c5..a86b46d3b1 100644 --- a/packages/extension/src/manifest.v3.json +++ b/packages/extension/src/manifest.v3.json @@ -3,7 +3,7 @@ "name": "Keplr", "description": "Keplr is a browser extension wallet for the Inter blockchain ecosystem.", - "version": "0.12.32", + "version": "0.12.47", "icons": { "16": "assets/icon-16.png", "48": "assets/icon-48.png", @@ -17,7 +17,14 @@ "background": { "service_worker": "background.bundle.js" }, - "permissions": ["storage", "notifications", "identity", "idle", "alarms"], + "permissions": [ + "storage", + "notifications", + "identity", + "idle", + "alarms", + "unlimitedStorage" + ], "content_scripts": [ { "matches": [""], diff --git a/packages/extension/src/pages/ibc-swap/components/swap-asset-info/index.tsx b/packages/extension/src/pages/ibc-swap/components/swap-asset-info/index.tsx index 2289e5d4aa..52cec7a6ae 100644 --- a/packages/extension/src/pages/ibc-swap/components/swap-asset-info/index.tsx +++ b/packages/extension/src/pages/ibc-swap/components/swap-asset-info/index.tsx @@ -14,7 +14,11 @@ import { } from "../../../../components/typography"; import styled, { useTheme } from "styled-components"; import { ColorPalette } from "../../../../styles"; -import { ChainImageFallback } from "../../../../components/image"; +import { + ChainImageFallback, + CurrencyImageFallback, + RawImageFallback, +} from "../../../../components/image"; import { AppCurrency } from "@keplr-wallet/types"; import { IBCSwapAmountConfig } from "../../../../hooks/ibc-swap"; import { useNavigate } from "react-router"; @@ -73,13 +77,20 @@ export const SwapAssetInfo: FunctionComponent<{ senderConfig: ISenderConfig; amountConfig: IBCSwapAmountConfig; + forceShowPrice?: boolean; onDestinationChainSelect?: ( chainId: string, coinMinimalDenom: string ) => void; }> = observer( - ({ type, senderConfig, amountConfig, onDestinationChainSelect }) => { - const { chainStore, queriesStore, priceStore } = useStore(); + ({ + type, + senderConfig, + amountConfig, + forceShowPrice, + onDestinationChainSelect, + }) => { + const { chainStore, queriesStore, priceStore, uiConfigStore } = useStore(); const theme = useTheme(); @@ -223,13 +234,16 @@ export const SwapAssetInfo: FunctionComponent<{ return `0 ${amountConfig.currency.coinDenom}`; } - return bal.balance - .maxDecimals(6) - .trim(true) - .shrink(true) - .inequalitySymbol(true) - .hideIBCMetadata(true) - .toString(); + return uiConfigStore.hideStringIfPrivacyMode( + bal.balance + .maxDecimals(6) + .trim(true) + .shrink(true) + .inequalitySymbol(true) + .hideIBCMetadata(true) + .toString(), + 2 + ); })(), } )} @@ -445,14 +459,22 @@ export const SwapAssetInfo: FunctionComponent<{ return ( - + {/* Currency가 없을 경우엔 대충 fallback 이미지로 처리한다 */} + {!currency ? ( + + ) : ( + + )} {(() => { - if (type === "from") { - if (!price) { + if (type === "from" || forceShowPrice) { + if (type === "from" && !price) { return null; } + if (type === "to") { + if (!priceStore.calculatePrice(amountConfig.outAmount)) { + return null; + } + } return ( { e.preventDefault(); + if (type !== "from") { + return; + } + if (!isPriceBased) { - if (price.toDec().lte(new Dec(0))) { + if (price!.toDec().lte(new Dec(0))) { setPriceValue(""); } else { setPriceValue( - price + price! .toDec() - .toString(price.options.maxDecimals) + .toString(price!.options.maxDecimals) .toString() ); } @@ -526,16 +557,20 @@ export const SwapAssetInfo: FunctionComponent<{ }} > - - + {type === "from" ? ( + + + + + ) : null} @@ -814,14 +859,8 @@ const SelectDestinationChainModal: FunctionComponent<{ > { const { @@ -58,11 +60,6 @@ export const IBCSwapPage: FunctionComponent = observer(() => { priceStore, } = useStore(); - useLayoutEffect(() => { - // 더 이상 new feature 소개가 안뜨도록 만든다. - uiConfigStore.setNeedShowIBCSwapFeatureAdded(false); - }, [uiConfigStore]); - const theme = useTheme(); const [searchParams, setSearchParams] = useSearchParams(); @@ -306,6 +303,65 @@ export const IBCSwapPage: FunctionComponent = observer(() => { ]); // ------ + const [isHighPriceImpact, setIsHighPriceImpact] = useState(false); + useEffectOnce(() => { + const disposal = autorun(() => { + if (ibcSwapConfigs.amountConfig.amount.length > 0) { + const amt = ibcSwapConfigs.amountConfig.amount[0]; + // priceStore.calculatePrice를 여기서 먼저 실행하는건 의도적인 행동임. + // 유저가 amount를 입력하기 전에 미리 fecth를 해놓기 위해서임. + const inPrice = priceStore.calculatePrice(amt, "usd"); + const outPrice = priceStore.calculatePrice( + ibcSwapConfigs.amountConfig.outAmount, + "usd" + ); + if (amt.toDec().gt(new Dec(0))) { + if ( + inPrice && + // in price가 아주 낮으면 오히려 price impact가 높아진다. + // 근데 이 경우는 전혀 치명적인 자산 상의 문제가 생기지 않으므로 0달러가 아니라 1달러가 넘어야 체크한다. + inPrice.toDec().gt(new Dec(1)) && + outPrice && + outPrice.toDec().gt(new Dec(0)) + ) { + if (inPrice.toDec().gt(outPrice.toDec())) { + const priceImpact = inPrice + .toDec() + .sub(outPrice.toDec()) + .quo(inPrice.toDec()) + .mul(new Dec(100)); + // price impact가 2.5% 이상이면 경고 + if (priceImpact.gt(new Dec(2.5))) { + setIsHighPriceImpact(true); + return; + } + } + } + } + } + + setIsHighPriceImpact(false); + }); + + return () => { + if (disposal) { + disposal(); + } + }; + }); + useEffectOnce(() => { + // 10초마다 price 자동 refresh + const intervalId = setInterval(() => { + if (priceStore.isInitialized && !priceStore.isFetching) { + priceStore.fetch(); + } + }, 1000 * 10); + + return () => { + clearInterval(intervalId); + }; + }); + const outCurrencyFetched = chainStore .getChain(outChainId) @@ -463,7 +519,9 @@ export const IBCSwapPage: FunctionComponent = observer(() => { }; }), ibcSwapConfigs.memoConfig.memo - ).withIBCPacketForwarding(channels); + ).withIBCPacketForwarding(channels, { + currencies: chainStore.getChain(chainId).currencies, + }); return await new InExtensionMessageRequester().sendMessage( BACKGROUND_PORT, msg @@ -494,7 +552,10 @@ export const IBCSwapPage: FunctionComponent = observer(() => { denom: amount.currency.coinMinimalDenom, }; }), - ibcSwapConfigs.memoConfig.memo + ibcSwapConfigs.memoConfig.memo, + { + currencies: chainStore.getChain(outChainId).currencies, + } ); return await new InExtensionMessageRequester().sendMessage( @@ -594,37 +655,10 @@ export const IBCSwapPage: FunctionComponent = observer(() => { params["outCurrencyCommonDenom"] = outCurrency.originCurrency.coinDenom; } - const getSwapRangeStr = (amount: { toDec: () => Dec }) => { - if (amount.toDec().lte(new Dec(0))) { - return "0"; - } - - const swapRanges = [ - 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, - 100000000, 1000000000, - ]; - let res = "unknown"; - for (let i = 0; i < swapRanges.length; i++) { - const range = swapRanges[i]; - const beforeRange = i > 0 ? swapRanges[i - 1] : 0; - if ( - amount.toDec().lte(new Dec(range)) && - amount.toDec().gt(new Dec(beforeRange)) - ) { - res = `${beforeRange}~${range}`; - break; - } - - if (i === swapRanges.length - 1) { - res = `${range}~`; - } - } - return res; - }; - params["inRange"] = getSwapRangeStr( + params["inRange"] = amountToAmbiguousString( ibcSwapConfigs.amountConfig.amount[0] ); - params["outRange"] = getSwapRangeStr( + params["outRange"] = amountToAmbiguousString( ibcSwapConfigs.amountConfig.outAmount ); @@ -636,14 +670,20 @@ export const IBCSwapPage: FunctionComponent = observer(() => { "usd" ); if (inCurrencyPrice) { - params["inFiatRange"] = getSwapRangeStr(inCurrencyPrice); + params["inFiatRange"] = + amountToAmbiguousString(inCurrencyPrice); + params["inFiatAvg"] = + amountToAmbiguousAverage(inCurrencyPrice); } const outCurrencyPrice = priceStore.calculatePrice( ibcSwapConfigs.amountConfig.outAmount, "usd" ); if (outCurrencyPrice) { - params["outFiatRange"] = getSwapRangeStr(outCurrencyPrice); + params["outFiatRange"] = + amountToAmbiguousString(outCurrencyPrice); + params["outFiatAvg"] = + amountToAmbiguousAverage(outCurrencyPrice); } new InExtensionMessageRequester().sendMessage( @@ -835,6 +875,7 @@ export const IBCSwapPage: FunctionComponent = observer(() => { type="to" senderConfig={ibcSwapConfigs.senderConfig} amountConfig={ibcSwapConfigs.amountConfig} + forceShowPrice={isHighPriceImpact} onDestinationChainSelect={(chainId, coinMinimalDenom) => { setSearchParams( (prev) => { @@ -862,6 +903,15 @@ export const IBCSwapPage: FunctionComponent = observer(() => { { + if (isHighPriceImpact) { + return new Error( + intl.formatMessage({ + id: "page.ibc-swap.warning.high-price-impact", + }) + ); + } + })()} /> @@ -883,7 +933,8 @@ const WarningGuideBox: FunctionComponent<{ amountConfig: IBCSwapAmountConfig; forceError?: Error; -}> = observer(({ amountConfig, forceError }) => { + forceWarning?: Error; +}> = observer(({ amountConfig, forceError, forceWarning }) => { const error: string | undefined = (() => { if (forceError) { return forceError.message || forceError.toString(); @@ -909,6 +960,10 @@ const WarningGuideBox: FunctionComponent<{ if (queryError) { return queryError.message || queryError.toString(); } + + if (forceWarning) { + return forceWarning.message || forceWarning.toString(); + } })(); // Collapse됐을때는 이미 error가 없어졌기 때문이다. diff --git a/packages/extension/src/pages/main/available.tsx b/packages/extension/src/pages/main/available.tsx index 57f78d2149..2dfe359e60 100644 --- a/packages/extension/src/pages/main/available.tsx +++ b/packages/extension/src/pages/main/available.tsx @@ -193,6 +193,7 @@ export const AvailableTabView: FunctionComponent<{ return ( ` @@ -125,6 +126,7 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( queriesStore, priceStore, keyRingStore, + uiConfigStore, } = useStore(); const intl = useIntl(); const theme = useTheme(); @@ -152,13 +154,29 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( const queryRewards = queries.cosmos.queryRewards.getQueryBech32Address(accountAddress); - if (queryRewards.stakableReward) { - res.push({ - token: queryRewards.stakableReward, - chainInfo, - isFetching: queryRewards.isFetching, - error: queryRewards.error, - }); + const targetDenom = (() => { + if (chainInfo.chainIdentifier === "dydx-mainnet") { + return "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5"; + } + + return chainInfo.stakeCurrency?.coinMinimalDenom; + })(); + + if (targetDenom) { + const currency = chainInfo.findCurrency(targetDenom); + if (currency) { + const reward = queryRewards.rewards.find( + (r) => r.currency.coinMinimalDenom === targetDenom + ); + if (reward) { + res.push({ + token: reward, + chainInfo, + isFetching: queryRewards.isFetching, + error: queryRewards.error, + }); + } + } } } @@ -265,12 +283,118 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( account.cosmos.makeWithdrawDelegationRewardTx(validatorAddresses); (async () => { - // At present, only assume that user can pay the fee with the stake currency. - // (Normally, user has stake currency because it is used for staking) - const feeCurrency = chainInfo.feeCurrencies.find( + let feeCurrency = chainInfo.feeCurrencies.find( (cur) => cur.coinMinimalDenom === chainInfo.stakeCurrency?.coinMinimalDenom ); + + if (chainInfo.hasFeature("osmosis-base-fee-beta") && feeCurrency) { + const queryBaseFee = queriesStore.get(chainInfo.chainId).osmosis + .queryBaseFee; + const queryRemoteBaseFeeStep = queriesStore.simpleQuery.queryGet<{ + low?: number; + average?: number; + high?: number; + }>( + "https://base-fee-step.s3.us-west-2.amazonaws.com/osmosis-base-fee-beta.json" + ); + + await queryBaseFee.waitFreshResponse(); + await queryRemoteBaseFeeStep.waitFreshResponse(); + + const baseFee = queryBaseFee.baseFee; + const remoteBaseFeeStep = queryRemoteBaseFeeStep.response; + if (baseFee) { + const low = remoteBaseFeeStep?.data.low + ? parseFloat( + baseFee.mul(new Dec(remoteBaseFeeStep.data.low)).toString(8) + ) + : feeCurrency.gasPriceStep?.low ?? DefaultGasPriceStep.low; + const average = Math.max( + low, + remoteBaseFeeStep?.data.average + ? parseFloat( + baseFee + .mul(new Dec(remoteBaseFeeStep.data.average)) + .toString(8) + ) + : feeCurrency.gasPriceStep?.average ?? + DefaultGasPriceStep.average + ); + const high = Math.max( + average, + remoteBaseFeeStep?.data.high + ? parseFloat( + baseFee + .mul(new Dec(remoteBaseFeeStep.data.high)) + .toString(8) + ) + : feeCurrency.gasPriceStep?.high ?? DefaultGasPriceStep.high + ); + + feeCurrency = { + ...feeCurrency, + gasPriceStep: { + low, + average, + high, + }, + }; + } + } + + if (!feeCurrency) { + let prev: + | { + balance: CoinPretty; + price: PricePretty | undefined; + } + | undefined; + + for (const chainFeeCurrency of chainInfo.feeCurrencies) { + const currency = await chainInfo.findCurrencyAsync( + chainFeeCurrency.coinMinimalDenom + ); + if (currency) { + const balance = queries.queryBalances + .getQueryBech32Address(account.bech32Address) + .getBalance(currency); + if (balance && balance.balance.toDec().gt(new Dec(0))) { + const price = await priceStore.waitCalculatePrice( + balance.balance, + "usd" + ); + + if (!prev) { + feeCurrency = currency; + prev = { + balance: balance.balance, + price, + }; + } else { + if (!prev.price) { + if (prev.balance.toDec().lt(balance.balance.toDec())) { + feeCurrency = currency; + prev = { + balance: balance.balance, + price, + }; + } + } else if (price) { + if (prev.price.toDec().lt(price.toDec())) { + feeCurrency = currency; + prev = { + balance: balance.balance, + price, + }; + } + } + } + } + } + } + } + if (feeCurrency) { try { const simulated = await tx.simulate(); @@ -287,10 +411,8 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( .toString(), }; - // coingecko로부터 캐시가 있거나 response를 최소한 한번은 받았다는 걸 보장한다. - await priceStore.waitResponse(); // USD 기준으로 average fee가 0.2달러를 넘으면 low로 설정해서 보낸다. - const averageFeePrice = priceStore.calculatePrice( + const averageFeePrice = await priceStore.waitCalculatePrice( new CoinPretty(feeCurrency, fee.amount), "usd" ); @@ -310,13 +432,23 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( ); } + // Ensure fee currency fetched before querying balance + const feeCurrencyFetched = await chainInfo.findCurrencyAsync( + feeCurrency.coinMinimalDenom + ); + if (!feeCurrencyFetched) { + state.setFailedReason( + new Error( + intl.formatMessage({ + id: "error.can-not-find-balance-for-fee-currency", + }) + ) + ); + return; + } const balance = queries.queryBalances .getQueryBech32Address(account.bech32Address) - .balances.find( - (bal) => - bal.currency.coinMinimalDenom === - feeCurrency.coinMinimalDenom - ); + .getBalance(feeCurrencyFetched); if (!balance) { state.setFailedReason( @@ -344,19 +476,52 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( return; } - const stakableReward = queryRewards.stakableReward; - if (!stakableReward) { - return; - } if ( - new Dec(stakableReward.toCoin().amount).lte(new Dec(fee.amount)) + (viewToken.token.toCoin().denom === fee.denom && + new Dec(viewToken.token.toCoin().amount).lte( + new Dec(fee.amount) + )) || + (await (async () => { + if (viewToken.token.toCoin().denom !== fee.denom) { + if ( + viewToken.token.currency.coinGeckoId && + feeCurrencyFetched.coinGeckoId + ) { + const rewardPrice = await priceStore.waitCalculatePrice( + viewToken.token, + "usd" + ); + const feePrice = await priceStore.waitCalculatePrice( + new CoinPretty(feeCurrencyFetched, fee.amount), + "usd" + ); + if ( + rewardPrice && + rewardPrice.toDec().gt(new Dec(0)) && + feePrice && + feePrice.toDec().gt(new Dec(0)) + ) { + if ( + rewardPrice + .toDec() + .mul(new Dec(1.2)) + .lte(feePrice.toDec()) + ) { + return true; + } + } + } + } + + return false; + })()) ) { console.log( `(${chainId}) Skip claim rewards. Fee: ${fee.amount}${ fee.denom } is greater than stakable reward: ${ - stakableReward.toCoin().amount - }${stakableReward.toCoin().denom}` + viewToken.token.toCoin().amount + }${viewToken.token.toCoin().denom}` ); state.setFailedReason( new Error( @@ -453,7 +618,7 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( state.setFailedReason( new Error( intl.formatMessage({ - id: "error.can-not-pay-for-fee-by-stake-currency", + id: "error.can-not-find-fee-for-claim-all", }) ) ); @@ -528,7 +693,10 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( : ColorPalette["gray-10"], }} > - {totalPrice ? totalPrice.separator(" ").toString() : "?"} + {uiConfigStore.hideStringIfPrivacyMode( + totalPrice ? totalPrice.separator(" ").toString() : "?", + 3 + )} @@ -633,7 +801,8 @@ const ClaimTokenItem: FunctionComponent<{ itemsLength: number; }> = observer(({ viewToken, state, itemsLength }) => { - const { analyticsStore, accountStore, queriesStore } = useStore(); + const { analyticsStore, accountStore, queriesStore, uiConfigStore } = + useStore(); const intl = useIntl(); const theme = useTheme(); @@ -690,6 +859,8 @@ const ClaimTokenItem: FunctionComponent<{ console.log(e); } + // TODO: gas price step이 고정되어있지 않은 경우에 대해서 처리 해야함 ex) osmosis-base-fee-beta + try { await tx.send( { @@ -758,13 +929,10 @@ const ClaimTokenItem: FunctionComponent<{ {viewToken.token.currency.coinImageUrl && ( - )} @@ -780,7 +948,17 @@ const ClaimTokenItem: FunctionComponent<{ : ColorPalette["gray-300"], }} > - {viewToken.token.currency.coinDenom} + {(() => { + if ("paths" in viewToken.token.currency) { + const originDenom = + viewToken.token.currency.originCurrency?.coinDenom; + if (originDenom) { + return `${originDenom} (${viewToken.chainInfo.chainName})`; + } + } + + return viewToken.token.currency.coinDenom; + })()} - {viewToken.token - .maxDecimals(6) - .shrink(true) - .inequalitySymbol(true) - .hideDenom(true) - .toString()} + {uiConfigStore.hideStringIfPrivacyMode( + viewToken.token + .maxDecimals(6) + .shrink(true) + .inequalitySymbol(true) + .hideDenom(true) + .toString(), + 2 + )} diff --git a/packages/extension/src/pages/main/components/deposit-modal/copy-address-scene.tsx b/packages/extension/src/pages/main/components/deposit-modal/copy-address-scene.tsx index dd92136949..002e216786 100644 --- a/packages/extension/src/pages/main/components/deposit-modal/copy-address-scene.tsx +++ b/packages/extension/src/pages/main/components/deposit-modal/copy-address-scene.tsx @@ -34,7 +34,13 @@ import Color from "color"; export const CopyAddressScene: FunctionComponent<{ close: () => void; }> = observer(({ close }) => { - const { chainStore, accountStore, keyRingStore, uiConfigStore } = useStore(); + const { + chainStore, + accountStore, + keyRingStore, + uiConfigStore, + analyticsStore, + } = useStore(); const intl = useIntl(); const theme = useTheme(); @@ -213,7 +219,38 @@ export const CopyAddressScene: FunctionComponent<{ color={ColorPalette["gray-300"]} style={{ textAlign: "center" }} > - + ( + { + e.preventDefault(); + + if (keyRingStore.selectedKeyInfo) { + analyticsStore.logEvent( + "click_menu_manageChainVisibility" + ); + browser.tabs + .create({ + url: `/register.html#?route=enable-chains&vaultId=${keyRingStore.selectedKeyInfo.id}&skipWelcome=true`, + }) + .then(() => { + window.close(); + }); + } + }} + > + {chunks} + + ), + }} + /> ) : null} @@ -437,14 +474,7 @@ const CopyAddressItem: FunctionComponent<{ - + - + {chainInfo.chainName} diff --git a/packages/extension/src/pages/main/components/ibc-history-view/index.tsx b/packages/extension/src/pages/main/components/ibc-history-view/index.tsx index 8ed9bdecdc..72809934aa 100644 --- a/packages/extension/src/pages/main/components/ibc-history-view/index.tsx +++ b/packages/extension/src/pages/main/components/ibc-history-view/index.tsx @@ -155,6 +155,10 @@ const IbcHistoryViewItem: FunctionComponent<{ return false; } + if (history.ibcHistory.some((h) => h.error != null)) { + return false; + } + return !history.ibcHistory.some((ibcHistory) => { return !ibcHistory.completed; }); @@ -188,7 +192,7 @@ const IbcHistoryViewItem: FunctionComponent<{ {(() => { - if (history.ibcHistory.find((h) => h.error != null)) { + if (failedChannelIndex >= 0) { return ( - {!historyCompleted - ? intl.formatMessage({ - id: isIBCSwap - ? "page.main.components.ibc-history-view.ibc-swap.item.pending" - : "page.main.components.ibc-history-view.item.pending", - }) - : intl.formatMessage({ - id: isIBCSwap - ? "page.main.components.ibc-history-view.ibc-swap.item.succeed" - : "page.main.components.ibc-history-view.item.succeed", - })} + {(() => { + if (failedChannelIndex >= 0) { + if ( + !history.ibcHistory + .slice(0, failedChannelIndex + 1) + .some((h) => !h.rewound) || + history.ibcHistory + .slice(0, failedChannelIndex + 1) + .some((h) => h.rewoundButNextRewindingBlocked) + ) { + return intl.formatMessage({ + id: "page.main.components.ibc-history-view.ibc-swap.item.refund.succeed", + }); + } + return intl.formatMessage({ + id: "page.main.components.ibc-history-view.ibc-swap.item.refund.pending", + }); + } + + return !historyCompleted + ? intl.formatMessage({ + id: isIBCSwap + ? "page.main.components.ibc-history-view.ibc-swap.item.pending" + : "page.main.components.ibc-history-view.item.pending", + }) + : intl.formatMessage({ + id: isIBCSwap + ? "page.main.components.ibc-history-view.ibc-swap.item.succeed" + : "page.main.components.ibc-history-view.item.succeed", + }); + })()}
{ @@ -494,16 +518,46 @@ const IbcHistoryViewItem: FunctionComponent<{ { let complete = false; - const errorIndex = history.ibcHistory.findIndex( - (h) => h.error != null - ); - if (errorIndex >= 0) { - complete = !history.ibcHistory - .slice(0, errorIndex + 1) - .find((h) => !h.rewound); + if (failedChannelIndex >= 0) { + complete = + !history.ibcHistory + .slice(0, failedChannelIndex + 1) + .find((h) => !h.rewound) || + history.ibcHistory.find( + (h) => h.rewoundButNextRewindingBlocked + ) != null; } if (isIBCSwap) { + if ("swapRefundInfo" in history && history.swapRefundInfo) { + return intl.formatMessage( + { + id: "page.main.components.ibc-history-view.ibc-swap.failed.after-swap.complete", + }, + { + chain: chainStore.getChain( + history.swapRefundInfo.chainId + ).chainName, + assets: history.swapRefundInfo.amount + .map((amount) => { + return new CoinPretty( + chainStore + .getChain(history.swapRefundInfo!.chainId) + .forceFindCurrency(amount.denom), + amount.amount + ) + .hideIBCMetadata(true) + .shrink(true) + .maxDecimals(6) + .inequalitySymbol(true) + .trim(true) + .toString(); + }) + .join(", "), + } + ); + } + return complete ? "page.main.components.ibc-history-view.ibc-swap.failed.complete" : "page.main.components.ibc-history-view.ibc-swap.failed.in-progress"; @@ -517,7 +571,28 @@ const IbcHistoryViewItem: FunctionComponent<{ - + { + if (historyCompleted) { + return true; + } + + if (failedChannelIndex >= 0) { + if ( + !history.ibcHistory + .slice(0, failedChannelIndex + 1) + .some((h) => !h.rewound) || + history.ibcHistory + .slice(0, failedChannelIndex + 1) + .some((h) => h.rewoundButNextRewindingBlocked) + ) { + return true; + } + } + + return false; + })()} + > {error ? ( diff --git a/packages/extension/src/pages/main/components/token-found-modal/index.tsx b/packages/extension/src/pages/main/components/token-found-modal/index.tsx index 2b47fe7cce..21a31bae2c 100644 --- a/packages/extension/src/pages/main/components/token-found-modal/index.tsx +++ b/packages/extension/src/pages/main/components/token-found-modal/index.tsx @@ -11,7 +11,10 @@ import { import { ColorPalette } from "../../../../styles"; import { Button } from "../../../../components/button"; import { Column, Columns } from "../../../../components/column"; -import { ChainImageFallback } from "../../../../components/image"; +import { + ChainImageFallback, + CurrencyImageFallback, +} from "../../../../components/image"; import { Stack } from "../../../../components/stack"; import { Checkbox } from "../../../../components/checkbox"; import { ArrowDownIcon, ArrowUpIcon } from "../../../../components/icon"; @@ -315,12 +318,9 @@ const FoundChainView: FunctionComponent<{ @@ -384,15 +384,17 @@ const FoundTokenView: FunctionComponent<{ chainId: string; asset: TokenScan["infos"][0]["assets"][0]; }> = observer(({ chainId, asset }) => { - const { chainStore } = useStore(); + const { chainStore, uiConfigStore } = useStore(); const theme = useTheme(); return ( - @@ -419,17 +421,20 @@ const FoundTokenView: FunctionComponent<{ : ColorPalette["gray-50"] } > - {new CoinPretty( - chainStore - .getChain(chainId) - .forceFindCurrency(asset.currency.coinMinimalDenom), - asset.amount - ) - .shrink(true) - .trim(true) - .maxDecimals(6) - .inequalitySymbol(true) - .toString()} + {uiConfigStore.hideStringIfPrivacyMode( + new CoinPretty( + chainStore + .getChain(chainId) + .forceFindCurrency(asset.currency.coinMinimalDenom), + asset.amount + ) + .shrink(true) + .trim(true) + .maxDecimals(6) + .inequalitySymbol(true) + .toString(), + 2 + )} ); diff --git a/packages/extension/src/pages/main/components/token/index.tsx b/packages/extension/src/pages/main/components/token/index.tsx index 9e12231b94..15e7446a5d 100644 --- a/packages/extension/src/pages/main/components/token/index.tsx +++ b/packages/extension/src/pages/main/components/token/index.tsx @@ -28,7 +28,7 @@ import { QuestionIcon, } from "../../../../components/icon"; import styled, { css, useTheme } from "styled-components"; -import { ChainImageFallback } from "../../../../components/image"; +import { CurrencyImageFallback } from "../../../../components/image"; import { Tooltip } from "../../../../components/tooltip"; import { DenomHelper } from "@keplr-wallet/common"; import { Tag } from "../../../../components/tag"; @@ -171,7 +171,7 @@ export const TokenItem: FunctionComponent = observer( copyAddress, hideBalance, }) => { - const { priceStore } = useStore(); + const { priceStore, uiConfigStore } = useStore(); const navigate = useNavigate(); const intl = useIntl(); const theme = useTheme(); @@ -268,13 +268,10 @@ export const TokenItem: FunctionComponent = observer( > - @@ -343,7 +340,10 @@ export const TokenItem: FunctionComponent = observer( }); } - return viewToken.error.message; + return ( + viewToken.error.message || + "Failed to query response from endpoint. Check again in a few minutes." + ); })()} > @@ -395,12 +395,15 @@ export const TokenItem: FunctionComponent = observer( : ColorPalette["gray-10"] } > - {viewToken.token - .hideDenom(true) - .maxDecimals(6) - .inequalitySymbol(true) - .shrink(true) - .toString()} + {uiConfigStore.hideStringIfPrivacyMode( + viewToken.token + .hideDenom(true) + .maxDecimals(6) + .inequalitySymbol(true) + .shrink(true) + .toString(), + 2 + )} ) : null} @@ -450,9 +453,12 @@ export const TokenItem: FunctionComponent = observer( return altSentence; } - return pricePretty - ? pricePretty.inequalitySymbol(true).toString() - : "-"; + return uiConfigStore.hideStringIfPrivacyMode( + pricePretty + ? pricePretty.inequalitySymbol(true).toString() + : "-", + 2 + ); })()} )} diff --git a/packages/extension/src/pages/main/index.tsx b/packages/extension/src/pages/main/index.tsx index 6b2ab2a09a..43863cfc0b 100644 --- a/packages/extension/src/pages/main/index.tsx +++ b/packages/extension/src/pages/main/index.tsx @@ -18,7 +18,11 @@ import { } from "./components"; import { Stack } from "../../components/stack"; import { CoinPretty, PricePretty } from "@keplr-wallet/unit"; -import { ArrowTopRightOnSquareIcon } from "../../components/icon"; +import { + ArrowTopRightOnSquareIcon, + EyeIcon, + EyeSlashIcon, +} from "../../components/icon"; import { Box } from "../../components/box"; import { Modal } from "../../components/modal"; import { DualChart } from "./components/chart"; @@ -28,18 +32,22 @@ import { ColorPalette } from "../../styles"; import { AvailableTabView } from "./available"; import { StakedTabView } from "./staked"; import { SearchTextInput } from "../../components/input"; -import { useSpringValue } from "@react-spring/web"; +import { animated, useSpringValue } from "@react-spring/web"; import { defaultSpringConfig } from "../../styles/spring"; import { IChainInfoImpl, QueryError } from "@keplr-wallet/stores"; import { Skeleton } from "../../components/skeleton"; import { FormattedMessage, useIntl } from "react-intl"; import { useGlobarSimpleBar } from "../../hooks/global-simplebar"; -import { useTheme } from "styled-components"; +import styled, { useTheme } from "styled-components"; import { IbcHistoryView } from "./components/ibc-history-view"; import { LayeredHorizontalRadioGroup } from "../../components/radio-group"; -import { YAxis } from "../../components/axis"; +import { XAxis, YAxis } from "../../components/axis"; import { DepositModal } from "./components/deposit-modal"; import { MainHeaderLayout } from "./layouts/header"; +import { amountToAmbiguousAverage } from "../../utils"; +import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; +import { LogAnalyticsEventMsg } from "@keplr-wallet/background"; +import { BACKGROUND_PORT } from "@keplr-wallet/router"; export interface ViewToken { token: CoinPretty; @@ -62,7 +70,8 @@ type TabStatus = "available" | "staked"; export const MainPage: FunctionComponent<{ setIsNotReady: (isNotReady: boolean) => void; }> = observer(({ setIsNotReady }) => { - const { analyticsStore, hugeQueriesStore, uiConfigStore } = useStore(); + const { analyticsStore, hugeQueriesStore, uiConfigStore, keyRingStore } = + useStore(); const isNotReady = useIsNotReady(); const intl = useIntl(); @@ -89,10 +98,18 @@ export const MainPage: FunctionComponent<{ } return result; }, [hugeQueriesStore.allKnownBalances]); - const availableChartWeight = - availableTotalPrice && !isNotReady + const availableChartWeight = (() => { + if (!isNotReady && uiConfigStore.isPrivacyMode) { + if (tabStatus === "available") { + return 1; + } + return 0; + } + + return availableTotalPrice && !isNotReady ? Number.parseFloat(availableTotalPrice.toDec().toString()) : 0; + })(); const stakedTotalPrice = useMemo(() => { let result: PricePretty | undefined; for (const bal of hugeQueriesStore.delegations) { @@ -115,10 +132,50 @@ export const MainPage: FunctionComponent<{ } return result; }, [hugeQueriesStore.delegations, hugeQueriesStore.unbondings]); - const stakedChartWeight = - stakedTotalPrice && !isNotReady + const stakedChartWeight = (() => { + if (!isNotReady && uiConfigStore.isPrivacyMode) { + if (tabStatus === "staked") { + return 1; + } + return 0; + } + + return stakedTotalPrice && !isNotReady ? Number.parseFloat(stakedTotalPrice.toDec().toString()) : 0; + })(); + + const lastTotalAvailableAmbiguousAvg = useRef(-1); + const lastTotalStakedAmbiguousAvg = useRef(-1); + useEffect(() => { + if (!isNotReady) { + const totalAvailableAmbiguousAvg = availableTotalPrice + ? amountToAmbiguousAverage(availableTotalPrice) + : 0; + const totalStakedAmbiguousAvg = stakedTotalPrice + ? amountToAmbiguousAverage(stakedTotalPrice) + : 0; + if ( + lastTotalAvailableAmbiguousAvg.current !== totalAvailableAmbiguousAvg || + lastTotalStakedAmbiguousAvg.current !== totalStakedAmbiguousAvg + ) { + new InExtensionMessageRequester().sendMessage( + BACKGROUND_PORT, + new LogAnalyticsEventMsg("user_properties", { + totalAvailableFiatAvg: totalAvailableAmbiguousAvg, + totalStakedFiatAvg: totalStakedAmbiguousAvg, + id: keyRingStore.selectedKeyInfo?.id, + keyType: keyRingStore.selectedKeyInfo?.insensitive[ + "keyRingType" + ] as string | undefined, + }) + ); + } + lastTotalAvailableAmbiguousAvg.current = totalAvailableAmbiguousAvg; + lastTotalStakedAmbiguousAvg.current = totalStakedAmbiguousAvg; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [availableTotalPrice, isNotReady, stakedTotalPrice]); const [isOpenDepositModal, setIsOpenDepositModal] = React.useState(false); const [isOpenBuy, setIsOpenBuy] = React.useState(false); @@ -168,6 +225,10 @@ export const MainPage: FunctionComponent<{ }); const globalSimpleBar = useGlobarSimpleBar(); + const animatedPrivacyModeHover = useSpringValue(0, { + config: defaultSpringConfig, + }); + return ( @@ -233,32 +294,90 @@ export const MainPage: FunctionComponent<{ }} > - - - {tabStatus === "available" - ? intl.formatMessage({ id: "page.main.chart.available" }) - : intl.formatMessage({ id: "page.main.chart.staked" })} - - - - -

- {tabStatus === "available" - ? availableTotalPrice?.toString() || "-" - : stakedTotalPrice?.toString() || "-"} -

-
+ { + if (!isNotReady) { + animatedPrivacyModeHover.start(isHover ? 1 : 0); + } else { + animatedPrivacyModeHover.set(0); + } + }} + > + + + + {tabStatus === "available" + ? intl.formatMessage({ + id: "page.main.chart.available", + }) + : intl.formatMessage({ + id: "page.main.chart.staked", + })} + + `${v * 1.25}rem` + ), + }} + > + + Math.max(0, (v - 0.3) * (10 / 3)) + ), + marginTop: "2px", + }} + onClick={(e) => { + e.preventDefault(); + + uiConfigStore.toggleIsPrivacyMode(); + }} + > + {uiConfigStore.isPrivacyMode ? ( + + ) : ( + + )} + + + + + + +

+ {uiConfigStore.hideStringIfPrivacyMode( + tabStatus === "available" + ? availableTotalPrice?.toString() || "-" + : stakedTotalPrice?.toString() || "-", + 4 + )} +

+
+
{tabStatus === "available" ? ( @@ -409,3 +528,20 @@ export const MainPage: FunctionComponent<{ ); }); + +const Styles = { + // hover style을 쉽게 넣으려고 그냥 styled-component로 만들었다. + PrivacyModeButton: styled.div` + color: ${(props) => + props.theme.mode === "light" + ? ColorPalette["gray-300"] + : ColorPalette["gray-400"]}; + + &:hover { + color: ${(props) => + props.theme.mode === "light" + ? ColorPalette["gray-200"] + : ColorPalette["gray-300"]}; + } + `, +}; diff --git a/packages/extension/src/pages/main/layouts/header.tsx b/packages/extension/src/pages/main/layouts/header.tsx index 14649668cd..3887396665 100644 --- a/packages/extension/src/pages/main/layouts/header.tsx +++ b/packages/extension/src/pages/main/layouts/header.tsx @@ -12,6 +12,12 @@ import { useTheme } from "styled-components"; import { Modal } from "../../../components/modal"; import { MenuBar } from "../components"; import { HeaderProps } from "../../../layouts/header/types"; +import { ColorPalette } from "../../../styles"; +import { YAxis } from "../../../components/axis"; +import { Body2, Subtitle3 } from "../../../components/typography"; +import { FormattedMessage, useIntl } from "react-intl"; +import { Gutter } from "../../../components/gutter"; +import { Button } from "../../../components/button"; export const MainHeaderLayout: FunctionComponent< PropsWithChildren< @@ -52,9 +58,20 @@ export const MainHeaderLayout: FunctionComponent< })(); const theme = useTheme(); + const intl = useIntl(); const [isOpenMenu, setIsOpenMenu] = React.useState(false); + const openMenu = () => { + setIsOpenMenu(true); + + if (uiConfigStore.newChainSuggestionConfig.newSuggestionChains.length > 0) { + uiConfigStore.newChainSuggestionConfig.turnOffSuggestionChains( + ...uiConfigStore.newChainSuggestionConfig.newSuggestionChains + ); + } + }; + return ( { @@ -87,13 +104,88 @@ export const MainHeaderLayout: FunctionComponent< return name; })()} left={ - setIsOpenMenu(true)} - cursor="pointer" - > - - + + {/* 일종의 padding left인데 cursor를 가지게 하면서 밑에서 tooltip도 함께 사용하기 위해서 다른 Box로 분리되어있음 */} + + + + + + { + return chainStore.getChain(chain).chainName; + }) + .join(", "), + }} + /> + + + + + + + +