From 0c82d9deaafa632d3277f246d0b1eee056a03226 Mon Sep 17 00:00:00 2001 From: agent-dominatrix <128386379+agent-dominatrix@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:01:47 +0530 Subject: [PATCH 1/5] feat: added queries for bridge history --- packages/background/src/keyring/service.ts | 33 +++++- .../src/pages/main/ledger-app-modal.tsx | 12 +- packages/stores/src/account/ethereum.ts | 6 +- packages/stores/src/common/types.ts | 7 ++ .../src/query/cosmwasm/bridge-history.ts | 80 ++++++++++++++ .../cosmwasm/bridge-reverse-swap-hash.ts | 71 ++++++++++++ packages/stores/src/query/cosmwasm/queries.ts | 16 +++ .../stores/src/query/evm/bridge-history.ts | 104 ++++++++++++++++++ .../src/query/evm/bridge-reverse-swap-hash.ts | 80 ++++++++++++++ packages/stores/src/query/evm/queries.ts | 17 +++ packages/stores/src/query/evm/types.ts | 8 ++ 11 files changed, 417 insertions(+), 17 deletions(-) create mode 100644 packages/stores/src/query/cosmwasm/bridge-history.ts create mode 100644 packages/stores/src/query/cosmwasm/bridge-reverse-swap-hash.ts create mode 100644 packages/stores/src/query/evm/bridge-history.ts create mode 100644 packages/stores/src/query/evm/bridge-reverse-swap-hash.ts diff --git a/packages/background/src/keyring/service.ts b/packages/background/src/keyring/service.ts index a00ae529ab..f6c810f48f 100644 --- a/packages/background/src/keyring/service.ts +++ b/packages/background/src/keyring/service.ts @@ -265,9 +265,16 @@ export class KeyRingService { const ethereumKeyFeatures = await this.chainsService.getChainEthereumKeyFeatures(chainId); + const isEvm = + (await this.chainsService.getChainInfo(chainId)).features?.includes( + "evm" + ) ?? false; + if (ethereumKeyFeatures.address || ethereumKeyFeatures.signing) { // Check the comment on the method itself. - this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + if (!isEvm) { + this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + } } return this.keyRing.getKey( @@ -308,10 +315,16 @@ export class KeyRingService { const coinType = await this.chainsService.getChainCoinType(chainId); const ethereumKeyFeatures = await this.chainsService.getChainEthereumKeyFeatures(chainId); + const isEvm = + (await this.chainsService.getChainInfo(chainId)).features?.includes( + "evm" + ) ?? false; if (ethereumKeyFeatures.address || ethereumKeyFeatures.signing) { // Check the comment on the method itself. - this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + if (!isEvm) { + this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + } } const key = await this.keyRing.getKey( @@ -476,10 +489,16 @@ export class KeyRingService { const coinType = await this.chainsService.getChainCoinType(chainId); const ethereumKeyFeatures = await this.chainsService.getChainEthereumKeyFeatures(chainId); + const isEvm = + (await this.chainsService.getChainInfo(chainId)).features?.includes( + "evm" + ) ?? false; if (ethereumKeyFeatures.address || ethereumKeyFeatures.signing) { // Check the comment on the method itself. - this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + if (!isEvm) { + this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + } } const key = await this.keyRing.getKey( @@ -567,10 +586,16 @@ export class KeyRingService { const coinType = await this.chainsService.getChainCoinType(chainId); const ethereumKeyFeatures = await this.chainsService.getChainEthereumKeyFeatures(chainId); + const isEvm = + (await this.chainsService.getChainInfo(chainId)).features?.includes( + "evm" + ) ?? false; if (ethereumKeyFeatures.address || ethereumKeyFeatures.signing) { // Check the comment on the method itself. - this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + if (!isEvm) { + this.keyRing.throwErrorIfEthermintWithLedgerButNotSupported(chainId); + } } const key = await this.keyRing.getKey( diff --git a/packages/fetch-extension/src/pages/main/ledger-app-modal.tsx b/packages/fetch-extension/src/pages/main/ledger-app-modal.tsx index ca086a691e..207d612e98 100644 --- a/packages/fetch-extension/src/pages/main/ledger-app-modal.tsx +++ b/packages/fetch-extension/src/pages/main/ledger-app-modal.tsx @@ -4,7 +4,7 @@ import style from "./ledger-app-modal.module.scss"; import { observer } from "mobx-react-lite"; import { useStore } from "../../stores"; import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; -import { BACKGROUND_PORT, KeplrError } from "@keplr-wallet/router"; +import { BACKGROUND_PORT } from "@keplr-wallet/router"; import { InitNonDefaultLedgerAppMsg, LedgerApp, @@ -33,14 +33,10 @@ export const LedgerAppModal: FunctionComponent = observer(() => { const isOpen = (() => { if ( accountInfo.rejectionReason && - accountInfo.rejectionReason instanceof KeplrError + accountInfo.rejectionReason.message === + "No Ethereum public key. Initialize Ethereum app on Ledger by selecting the chain in the extension" ) { - if ( - accountInfo.rejectionReason.module === "keyring" && - accountInfo.rejectionReason.code === 901 - ) { - return true; - } + return true; } return false; diff --git a/packages/stores/src/account/ethereum.ts b/packages/stores/src/account/ethereum.ts index 1038a4d8a6..c7f7ef1bae 100644 --- a/packages/stores/src/account/ethereum.ts +++ b/packages/stores/src/account/ethereum.ts @@ -100,7 +100,6 @@ export class EthereumAccountImpl { } return this.makeEthereumTx("send", { - from: this.base.ethereumHexAddress, to: recipient, value: actualAmount, }); @@ -108,7 +107,6 @@ export class EthereumAccountImpl { return this.makeEthereumTx( "send", { - from: this.base.ethereumHexAddress, to: denomHelper.contractAddress, data: erc20MetadataInterface.encodeFunctionData("transfer", [ recipient, @@ -187,7 +185,7 @@ export class EthereumAccountImpl { jsonrpc: "2.0", id: "1", method: "eth_estimateGas", - params: [params], + params: [{ from: this.base.ethereumHexAddress, ...params }], }); if (result.data.error && result.data.error.message) { @@ -350,7 +348,6 @@ export class EthereumAccountImpl { return this.makeEthereumTx( "approval", { - from: this.base.ethereumHexAddress, to: new DenomHelper(currency.coinMinimalDenom).contractAddress, data: erc20MetadataInterface.encodeFunctionData("approve", [ spender, @@ -385,7 +382,6 @@ export class EthereumAccountImpl { return this.makeEthereumTx( "nativeBridgeSend", { - from: this.base.ethereumHexAddress, to: this.queries.evm.queryNativeFetBridge.nativeBridgeAddress, data: nativeFetBridgeInterface.encodeFunctionData("swap", [ actualAmount, diff --git a/packages/stores/src/common/types.ts b/packages/stores/src/common/types.ts index 1db7aee7bd..790751f287 100644 --- a/packages/stores/src/common/types.ts +++ b/packages/stores/src/common/types.ts @@ -18,3 +18,10 @@ export type CoinPrimitive = { denom: string; amount: string; }; + +export interface BridgeHistory { + to: string; + amount: string; + transactionHash: string | null; + swapId: string; +} diff --git a/packages/stores/src/query/cosmwasm/bridge-history.ts b/packages/stores/src/query/cosmwasm/bridge-history.ts new file mode 100644 index 0000000000..3f6d90b205 --- /dev/null +++ b/packages/stores/src/query/cosmwasm/bridge-history.ts @@ -0,0 +1,80 @@ +import { KVStore } from "@keplr-wallet/common"; +import Axios from "axios"; +import { computed, makeObservable } from "mobx"; +import { ChainGetter, HasMapStore, ObservableJsonRPCQuery } from "../../common"; +import { BridgeHistory } from "../../common/types"; +import { ObservableQueryNativeFetCosmosBridge } from "./native-fet-bridge"; +import { Tx } from "@keplr-wallet/proto-types/cosmos/tx/v1beta1/tx"; +Tx; +export class ObservableBridgeHistoryInner extends ObservableJsonRPCQuery { + constructor( + kvStore: KVStore, + chainGetter: ChainGetter, + protected readonly nativeBridge: ObservableQueryNativeFetCosmosBridge, + protected readonly bech32Address: string + ) { + const fetchhubUrl = chainGetter.getChain("fetchhub-4").rpc; + + const instance = Axios.create({ + ...{ + baseURL: fetchhubUrl, + }, + }); + + super(kvStore, instance, "", "tx_search", [ + `wasm._contract_address='${nativeBridge.nativeBridgeAddress}' AND wasm.action='swap' AND message.sender='${bech32Address}'`, + false, + "1", + "3000", + "asc", + ]); + + makeObservable(this); + } + + protected override canFetch(): boolean { + return super.canFetch() && this.bech32Address !== ""; + } + + @computed + get history(): BridgeHistory[] { + console.log("$$$$$", this.error); + if (!this.response || !this.response.data) { + return []; + } + + return this.response.data.txs.map((tx: any): BridgeHistory => { + const wasmData = JSON.parse(tx.tx_result.log)[0].events.find( + (e: any) => e.type === "wasm" + ); + + return { + to: wasmData.attributes.find((a: any) => a.key === "destination").value, + amount: wasmData.attributes.find((a: any) => a.key === "amount").value, + swapId: wasmData.attributes.find((a: any) => a.key === "swap_id").value, + transactionHash: tx.hash, + }; + }); + } +} + +export class ObservableQueryBridgeHistory extends HasMapStore { + constructor( + kvStore: KVStore, + chainGetter: ChainGetter, + nativeBridge: ObservableQueryNativeFetCosmosBridge + ) { + super((bech32address) => { + return new ObservableBridgeHistoryInner( + kvStore, + chainGetter, + nativeBridge, + bech32address + ); + }); + } + + getBridgeHistory(bech32address: string): ObservableBridgeHistoryInner { + return super.get(bech32address); + } +} diff --git a/packages/stores/src/query/cosmwasm/bridge-reverse-swap-hash.ts b/packages/stores/src/query/cosmwasm/bridge-reverse-swap-hash.ts new file mode 100644 index 0000000000..f25a458d0f --- /dev/null +++ b/packages/stores/src/query/cosmwasm/bridge-reverse-swap-hash.ts @@ -0,0 +1,71 @@ +import { KVStore } from "@keplr-wallet/common"; +import Axios from "axios"; +import { computed, makeObservable } from "mobx"; +import { ChainGetter, HasMapStore, ObservableJsonRPCQuery } from "../../common"; +import { ObservableQueryNativeFetCosmosBridge } from "./native-fet-bridge"; +import { Tx } from "@keplr-wallet/proto-types/cosmos/tx/v1beta1/tx"; +Tx; +export class ObservableBridgeReverseSwapHashInner extends ObservableJsonRPCQuery { + constructor( + kvStore: KVStore, + chainGetter: ChainGetter, + protected readonly nativeBridge: ObservableQueryNativeFetCosmosBridge, + protected readonly swapId: string + ) { + const fetchhubUrl = chainGetter.getChain("fetchhub-4").rpc; + + const instance = Axios.create({ + ...{ + baseURL: fetchhubUrl, + }, + }); + + super(kvStore, instance, "", "tx_search", [ + `wasm._contract_address='${nativeBridge.nativeBridgeAddress}' AND wasm.action='reverse_swap' AND wasm.rid='${swapId}'`, + false, + "1", + "3000", + "asc", + ]); + + makeObservable(this); + } + + @computed + get hash(): string | null { + if (!this.response || !this.response.data || this.response.data.txs === 0) { + return null; + } + + const txs = this.response.data.txs.filter( + (tx: any) => tx.tx_result && tx.tx_result.code === 0 + ); + + if (txs.length === 0) { + return null; + } + + return this.response.data.txs[0].hash; + } +} + +export class ObservableQueryBridgeReverseSwapHash extends HasMapStore { + constructor( + kvStore: KVStore, + chainGetter: ChainGetter, + nativeBridge: ObservableQueryNativeFetCosmosBridge + ) { + super((swapId) => { + return new ObservableBridgeReverseSwapHashInner( + kvStore, + chainGetter, + nativeBridge, + swapId + ); + }); + } + + getReverseSwapHash(swapId: string): ObservableBridgeReverseSwapHashInner { + return super.get(swapId); + } +} diff --git a/packages/stores/src/query/cosmwasm/queries.ts b/packages/stores/src/query/cosmwasm/queries.ts index 8d61512dfb..6721233300 100644 --- a/packages/stores/src/query/cosmwasm/queries.ts +++ b/packages/stores/src/query/cosmwasm/queries.ts @@ -5,6 +5,8 @@ import { ObservableQueryCw20ContractInfo } from "./cw20-contract-info"; import { DeepReadonly } from "utility-types"; import { ObservableQueryCw20BalanceRegistry } from "./cw20-balance"; import { ObservableQueryNativeFetCosmosBridge } from "./native-fet-bridge"; +import { ObservableQueryBridgeHistory } from "./bridge-history"; +import { ObservableQueryBridgeReverseSwapHash } from "./bridge-reverse-swap-hash"; export interface CosmwasmQueries { cosmwasm: CosmwasmQueriesImpl; @@ -38,6 +40,8 @@ export const CosmwasmQueries = { export class CosmwasmQueriesImpl { public readonly querycw20ContractInfo: DeepReadonly; public readonly queryNativeFetBridge: DeepReadonly; + public readonly queryBridgeHistory: DeepReadonly; + public readonly queryBridgeReverseSwapHash: DeepReadonly; constructor( base: QueriesSetBase, @@ -59,5 +63,17 @@ export class CosmwasmQueriesImpl { kvStore, chainGetter ); + + this.queryBridgeHistory = new ObservableQueryBridgeHistory( + kvStore, + chainGetter, + this.queryNativeFetBridge + ); + + this.queryBridgeReverseSwapHash = new ObservableQueryBridgeReverseSwapHash( + kvStore, + chainGetter, + this.queryNativeFetBridge + ); } } diff --git a/packages/stores/src/query/evm/bridge-history.ts b/packages/stores/src/query/evm/bridge-history.ts new file mode 100644 index 0000000000..837ab359f9 --- /dev/null +++ b/packages/stores/src/query/evm/bridge-history.ts @@ -0,0 +1,104 @@ +import { KVStore } from "@keplr-wallet/common"; +import Axios from "axios"; +import { computed, makeObservable } from "mobx"; +import { + ChainGetter, + HasMapStore, + ObservableJsonRPCQuery, + nativeFetBridgeInterface, +} from "../../common"; +import { NativeBridgeLogResponse } from "./types"; +import { BridgeHistory } from "../../common/types"; +import { BigNumber } from "@ethersproject/bignumber"; +import { ObservableQueryNativeFetEthBrige } from "./native-fet-bridge"; +import { Bech32Address } from "@keplr-wallet/cosmos"; + +export class ObservableBridgeHistoryInner extends ObservableJsonRPCQuery< + NativeBridgeLogResponse[] +> { + constructor( + kvStore: KVStore, + chainId: string, + chainGetter: ChainGetter, + protected readonly nativeBridge: ObservableQueryNativeFetEthBrige, + protected readonly bech32Address: string + ) { + const ethereumURL = chainGetter.getChain("1").rpc; + + const instance = Axios.create({ + ...{ + baseURL: ethereumURL, + }, + }); + + const ethereumHexAddress = bech32Address + ? Bech32Address.fromBech32( + bech32Address, + chainGetter.getChain(chainId).bech32Config.bech32PrefixAccAddr + ).toHex(true) + : "0x0000000000000000000000000000000000000000"; + + super(kvStore, instance, "", "eth_getLogs", [ + { + address: nativeBridge.nativeBridgeAddress, + topics: nativeFetBridgeInterface.encodeFilterTopics("Swap", [ + null, + ethereumHexAddress, + ]), + fromBlock: BigNumber.from("12130113").toHexString(), + toBlock: "pending", + }, + ]); + + makeObservable(this); + } + + protected override canFetch(): boolean { + return super.canFetch() && this.bech32Address !== ""; + } + + @computed + get history(): BridgeHistory[] { + if (!this.response) { + return []; + } + + return this.response.data + .filter((d) => !d.removed) + .map((d): BridgeHistory => { + const eventArgs = nativeFetBridgeInterface.parseLog({ + data: d.data, + topics: d.topics, + }).args; + return { + to: eventArgs[3], + amount: eventArgs[4].toString(), + swapId: eventArgs[0].toString(), + transactionHash: d.transactionHash, + }; + }); + } +} + +export class ObservableQueryBridgeHistory extends HasMapStore { + constructor( + kvStore: KVStore, + chainId: string, + chainGetter: ChainGetter, + nativeBridge: ObservableQueryNativeFetEthBrige + ) { + super((bech32address) => { + return new ObservableBridgeHistoryInner( + kvStore, + chainId, + chainGetter, + nativeBridge, + bech32address + ); + }); + } + + getBridgeHistory(bech32address: string): ObservableBridgeHistoryInner { + return super.get(bech32address); + } +} diff --git a/packages/stores/src/query/evm/bridge-reverse-swap-hash.ts b/packages/stores/src/query/evm/bridge-reverse-swap-hash.ts new file mode 100644 index 0000000000..8466a671a1 --- /dev/null +++ b/packages/stores/src/query/evm/bridge-reverse-swap-hash.ts @@ -0,0 +1,80 @@ +import { KVStore } from "@keplr-wallet/common"; +import Axios from "axios"; +import { computed, makeObservable } from "mobx"; +import { + ChainGetter, + HasMapStore, + ObservableJsonRPCQuery, + nativeFetBridgeInterface, +} from "../../common"; +import { NativeBridgeLogResponse } from "./types"; +import { BigNumber } from "@ethersproject/bignumber"; +import { ObservableQueryNativeFetEthBrige } from "./native-fet-bridge"; + +export class ObservableBridgeReverseSwapHashInner extends ObservableJsonRPCQuery< + NativeBridgeLogResponse[] +> { + constructor( + kvStore: KVStore, + chainGetter: ChainGetter, + protected readonly nativeBridge: ObservableQueryNativeFetEthBrige, + protected readonly swapId: string + ) { + const ethereumURL = chainGetter.getChain("1").rpc; + + const instance = Axios.create({ + ...{ + baseURL: ethereumURL, + }, + }); + + super(kvStore, instance, "", "eth_getLogs", [ + { + address: nativeBridge.nativeBridgeAddress, + topics: nativeFetBridgeInterface.encodeFilterTopics("ReverseSwap", [ + swapId, + ]), + fromBlock: BigNumber.from("12130113").toHexString(), + toBlock: "pending", + }, + ]); + + makeObservable(this); + } + + @computed + get hash(): string | null { + if (!this.response || this.response.data.length === 0) { + return null; + } + + const events = this.response.data.filter((d) => !d.removed); + + if (events.length === 0) { + return null; + } + + return events[0].transactionHash; + } +} + +export class ObservableQueryBridgeReverseSwapHash extends HasMapStore { + constructor( + kvStore: KVStore, + chainGetter: ChainGetter, + nativeBridge: ObservableQueryNativeFetEthBrige + ) { + super((swapId) => { + return new ObservableBridgeReverseSwapHashInner( + kvStore, + chainGetter, + nativeBridge, + swapId + ); + }); + } + + getReverseSwapHash(swapId: string): ObservableBridgeReverseSwapHashInner { + return super.get(swapId); + } +} diff --git a/packages/stores/src/query/evm/queries.ts b/packages/stores/src/query/evm/queries.ts index c1a8a60530..168eb745f6 100644 --- a/packages/stores/src/query/evm/queries.ts +++ b/packages/stores/src/query/evm/queries.ts @@ -10,6 +10,8 @@ import { import { ObservableQueryErc20BalanceRegistry } from "./erc20-balance"; import { ObservableQueryNativeFetEthBrige } from "./native-fet-bridge"; import { ObservableQueryGasFees } from "./eth-gas-fees"; +import { ObservableQueryBridgeHistory } from "./bridge-history"; +import { ObservableQueryBridgeReverseSwapHash } from "./bridge-reverse-swap-hash"; export interface EvmQueries { evm: EvmQueriesImpl; @@ -40,6 +42,8 @@ export class EvmQueriesImpl { public readonly queryNativeFetBridge: DeepReadonly; public readonly queryERC20Allowance: DeepReadonly; public readonly queryEthGasFees: DeepReadonly; + public readonly queryBridgeHistory: DeepReadonly; + public readonly queryBridgeReverseSwapHash: DeepReadonly; constructor( base: QueriesSetBase, @@ -73,5 +77,18 @@ export class EvmQueriesImpl { ); this.queryEthGasFees = new ObservableQueryGasFees(kvStore); + + this.queryBridgeHistory = new ObservableQueryBridgeHistory( + kvStore, + chainId, + chainGetter, + this.queryNativeFetBridge + ); + + this.queryBridgeReverseSwapHash = new ObservableQueryBridgeReverseSwapHash( + kvStore, + chainGetter, + this.queryNativeFetBridge + ); } } diff --git a/packages/stores/src/query/evm/types.ts b/packages/stores/src/query/evm/types.ts index a3608d54b2..5cfe401dba 100644 --- a/packages/stores/src/query/evm/types.ts +++ b/packages/stores/src/query/evm/types.ts @@ -30,3 +30,11 @@ export interface EthGasFeeInfo { average: string; high: string; } + +export interface NativeBridgeLogResponse { + address: string; + data: string; + removed: boolean; + topics: string[]; + transactionHash: string | null; +} From 09dc6056553ae437dd9cd1b01bf60ffc57c89fc5 Mon Sep 17 00:00:00 2001 From: agent-dominatrix <128386379+agent-dominatrix@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:33:04 +0530 Subject: [PATCH 2/5] fix: analytics and gov proposals --- packages/background/src/updater/service.ts | 1 + packages/fetch-extension/src/pages/validator/stake.tsx | 8 +++++++- packages/fetch-extension/src/pages/validator/transfer.tsx | 7 ++++++- packages/fetch-extension/src/pages/validator/unstake.tsx | 7 ++++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/background/src/updater/service.ts b/packages/background/src/updater/service.ts index 72f308f224..3aeb805742 100644 --- a/packages/background/src/updater/service.ts +++ b/packages/background/src/updater/service.ts @@ -36,6 +36,7 @@ export class ChainUpdaterService { if (updatedChainInfo) { chainInfo = { ...updatedChainInfo, + govUrl: updatedChainInfo.govUrl || origin.govUrl, walletUrlForStaking: updatedChainInfo.walletUrlForStaking || origin.walletUrlForStaking, features: (() => { diff --git a/packages/fetch-extension/src/pages/validator/stake.tsx b/packages/fetch-extension/src/pages/validator/stake.tsx index 7ffe2f7f72..eaa8223f21 100644 --- a/packages/fetch-extension/src/pages/validator/stake.tsx +++ b/packages/fetch-extension/src/pages/validator/stake.tsx @@ -20,7 +20,8 @@ import style from "./style.module.scss"; export const Stake: FunctionComponent<{ validatorAddress: string }> = observer( ({ validatorAddress }) => { const navigate = useNavigate(); - const { chainStore, accountStore, queriesStore } = useStore(); + const { chainStore, accountStore, queriesStore, analyticsStore } = + useStore(); const account = accountStore.getAccount(chainStore.current.chainId); const sendConfigs = useDelegateTxConfig( @@ -90,6 +91,11 @@ export const Stake: FunctionComponent<{ validatorAddress: string }> = observer( duration: 0.25, }, }); + + analyticsStore.logEvent("Stake tx broadcasted", { + chainId: chainStore.current.chainId, + chainName: chainStore.current.chainName, + }); }, onFulfill: (tx: any) => { const istxnSuccess = tx.code ? false : true; diff --git a/packages/fetch-extension/src/pages/validator/transfer.tsx b/packages/fetch-extension/src/pages/validator/transfer.tsx index 5c03ad4f77..7fcf2748a2 100644 --- a/packages/fetch-extension/src/pages/validator/transfer.tsx +++ b/packages/fetch-extension/src/pages/validator/transfer.tsx @@ -33,7 +33,7 @@ export const Transfer: FunctionComponent<{ balance: CoinPretty; }> = observer(({ validatorAddress, validatorsList, balance }) => { const navigate = useNavigate(); - const { chainStore, accountStore, queriesStore } = useStore(); + const { chainStore, accountStore, queriesStore, analyticsStore } = useStore(); const account = accountStore.getAccount(chainStore.current.chainId); const [selectedValidator, setSelectedValidator] = useState( validatorsList[0] @@ -94,6 +94,11 @@ export const Transfer: FunctionComponent<{ duration: 0.25, }, }); + + analyticsStore.logEvent("Redelegate tx broadcasted", { + chainId: chainStore.current.chainId, + chainName: chainStore.current.chainName, + }); }, onFulfill: (tx: any) => { const istxnSuccess = tx.code ? false : true; diff --git a/packages/fetch-extension/src/pages/validator/unstake.tsx b/packages/fetch-extension/src/pages/validator/unstake.tsx index a3dda5272b..76684f655c 100644 --- a/packages/fetch-extension/src/pages/validator/unstake.tsx +++ b/packages/fetch-extension/src/pages/validator/unstake.tsx @@ -20,7 +20,7 @@ export const Unstake: FunctionComponent<{ validatorAddress: string; }> = observer(({ validatorAddress }) => { const navigate = useNavigate(); - const { chainStore, accountStore, queriesStore } = useStore(); + const { chainStore, accountStore, queriesStore, analyticsStore } = useStore(); const account = accountStore.getAccount(chainStore.current.chainId); const sendConfigs = useUndelegateTxConfig( @@ -83,6 +83,11 @@ export const Unstake: FunctionComponent<{ duration: 0.25, }, }); + + analyticsStore.logEvent("Unstake tx broadcasted", { + chainId: chainStore.current.chainId, + chainName: chainStore.current.chainName, + }); }, onFulfill: (tx: any) => { const istxnSuccess = tx.code ? false : true; From c3633ea1a8950d788e428453c5afd2541b43d58c Mon Sep 17 00:00:00 2001 From: agent-dominatrix <128386379+agent-dominatrix@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:13:46 +0530 Subject: [PATCH 3/5] feat: bridge history UI --- packages/fetch-extension/src/index.tsx | 5 + .../src/pages/bridge/bridge-history.tsx | 197 ++++++++++++++++++ .../src/pages/bridge/index.tsx | 15 ++ .../src/pages/bridge/style.module.scss | 90 ++++++++ .../public/assets/svg/fetch_logo_black.svg | 1 + 5 files changed, 308 insertions(+) create mode 100644 packages/fetch-extension/src/pages/bridge/bridge-history.tsx create mode 100644 packages/fetch-extension/src/public/assets/svg/fetch_logo_black.svg diff --git a/packages/fetch-extension/src/index.tsx b/packages/fetch-extension/src/index.tsx index 186bf25c6a..9323f5d693 100644 --- a/packages/fetch-extension/src/index.tsx +++ b/packages/fetch-extension/src/index.tsx @@ -103,6 +103,7 @@ import { PropsalVoteStatus } from "./pages/proposals/proposal-vote-status"; import { FetchnameService } from "./pages/fetch-name-service"; import { DomainDetails } from "./pages/fetch-name-service/domain-details"; import { BridgePage } from "./pages/bridge"; +import { BridgeHistoryView } from "./pages/bridge/bridge-history"; window.keplr = new Keplr( manifest.version, @@ -211,6 +212,10 @@ ReactDOM.render( element={} /> } /> + } + /> } /> { + const navigate = useNavigate(); + + const { chainStore, accountStore, queriesStore } = useStore(); + const accountInfo = accountStore.getAccount(chainStore.current.chainId); + + const isEvm = chainStore.current.features?.includes("evm") ?? false; + const currentQueriesStore = queriesStore.get(chainStore.current.chainId); + + const currentChainBridgeHistoryQuery = isEvm + ? currentQueriesStore.evm.queryBridgeHistory + : currentQueriesStore.cosmwasm.queryBridgeHistory; + const bridgeHistory = currentChainBridgeHistoryQuery.getBridgeHistory( + accountInfo.bech32Address + ); + + return ( + { + navigate(-1); + }} + showBottomMenu={false} + > +
+ {bridgeHistory.isFetching ? ( +
+ +
+ ) : bridgeHistory.history.length === 0 ? ( +
+

+ +

+
+ ) : ( + bridgeHistory.history + .reverse() + .map((history) => ( + + )) + )} +
+
+ ); +}); + +const BridgeStatus: FunctionComponent<{ history: BridgeHistory }> = observer( + ({ history }) => { + const { chainStore, queriesStore } = useStore(); + const isEvm = chainStore.current.features?.includes("evm") ?? false; + const currentQueriesStore = queriesStore.get(chainStore.current.chainId); + + const counterChainSwapStatusQuery = isEvm + ? currentQueriesStore.cosmwasm.queryBridgeReverseSwapHash + : currentQueriesStore.evm.queryBridgeReverseSwapHash; + const reverseSwapHash = counterChainSwapStatusQuery.getReverseSwapHash( + history.swapId + ); + const fetCurrency = chainStore.current.currencies.find( + (c) => c.coinDenom === "FET" + ); + + return ( +
+
+

{`Swap id #${history.swapId}`}

+

{`Send ${ + fetCurrency + ? new CoinPretty(fetCurrency, history.amount) + .maxDecimals(2) + .toString() + : "0 FET" + }`}

+

+ To: {Bech32Address.shortenAddress(history.to, 22, !isEvm)} +

+
+ +
+ {reverseSwapHash.isFetching ? ( + + ) : ( +
+ + + +
+ )} +
+
+ ); + } +); diff --git a/packages/fetch-extension/src/pages/bridge/index.tsx b/packages/fetch-extension/src/pages/bridge/index.tsx index 790301af8e..74f1a653f1 100644 --- a/packages/fetch-extension/src/pages/bridge/index.tsx +++ b/packages/fetch-extension/src/pages/bridge/index.tsx @@ -7,6 +7,7 @@ import { EthereumBridge } from "./ethereum-bridge"; import { FetchhubBridge } from "./fetchhub-bridge"; import { HeaderLayout } from "@layouts/header-layout"; import { Dec, IntPretty } from "@keplr-wallet/unit"; +import { Button } from "reactstrap"; export const BridgePage: FunctionComponent = observer(() => { const { chainStore, queriesStore } = useStore(); @@ -46,6 +47,20 @@ export const BridgePage: FunctionComponent = observer(() => { onBackButton={() => { navigate(-1); }} + rightRenderer={ + + } > {isLoading ? (

diff --git a/packages/fetch-extension/src/pages/bridge/style.module.scss b/packages/fetch-extension/src/pages/bridge/style.module.scss index bd86df3f06..6da3738c46 100644 --- a/packages/fetch-extension/src/pages/bridge/style.module.scss +++ b/packages/fetch-extension/src/pages/bridge/style.module.scss @@ -69,3 +69,93 @@ text-decoration: underline; color: #555555; } + +.historyBtn { + display: flex; + align-items: center; + padding: 12px; + margin: 18px; +} + +.bHistory { + display: flex; + padding: 10px; + line-height: 20px; + justify-content: space-between; + margin: 12px 0; + background: #ffffff; + .hContent { + width: 83.5%; + .sId { + font-weight: 400; + font-size: 10px; + color: #808da0; + margin: 0; + line-height: 22px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .hTitle { + font-weight: 400; + padding-bottom: 10px; + font-size: 16px; + color: #525f7f; + margin: 0; + line-height: 22px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-right: 10px; + &::first-letter { + text-transform: capitalize; + } + } + + .hDesc { + font-weight: 400; + font-size: 15px; + color: #808da0; + margin: 0; + line-height: 22px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .hStatus { + display: flex; + width: 16.5%; + margin-top: 2px; + position: relative; + } + + .statusButton { + display: flex; + padding: 3px; + + .ethLogo { + height: 20px; + display: flex; + margin-right: 1px; + } + + .fetLogo { + height: 15px; + display: flex; + margin: 2px 4px 0 2px; + } + + .status { + display: flex; + height: 20px; + } + } + + .arrowIcon { + transform: rotate(90deg); + margin: -5px 0 0 15px; + } +} diff --git a/packages/fetch-extension/src/public/assets/svg/fetch_logo_black.svg b/packages/fetch-extension/src/public/assets/svg/fetch_logo_black.svg new file mode 100644 index 0000000000..0e57ab2c10 --- /dev/null +++ b/packages/fetch-extension/src/public/assets/svg/fetch_logo_black.svg @@ -0,0 +1 @@ + \ No newline at end of file From 9055293afe9b8d7ab096f97ce976fe72f343fdc8 Mon Sep 17 00:00:00 2001 From: agent-dominatrix <128386379+agent-dominatrix@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:25:35 +0530 Subject: [PATCH 4/5] feat: prettify txn details --- .../fetch-extension/src/languages/en.json | 2 +- .../src/pages/bridge/bridge-history.tsx | 12 ++ .../src/pages/bridge/style.module.scss | 6 + .../fetch-extension/src/pages/send/index.tsx | 1 - .../fetch-extension/src/pages/sign/adr-36.tsx | 126 ++++++++++++------ .../src/pages/sign/data-tab.tsx | 33 ++++- .../fetch-extension/src/pages/sign/evm.tsx | 90 +++++++++++++ .../fetch-extension/src/pages/sign/index.tsx | 7 +- .../src/pages/sign/messages.tsx | 52 +++++++- .../src/pages/sign/style.module.scss | 3 +- 10 files changed, 276 insertions(+), 56 deletions(-) create mode 100644 packages/fetch-extension/src/pages/sign/evm.tsx diff --git a/packages/fetch-extension/src/languages/en.json b/packages/fetch-extension/src/languages/en.json index 080f147981..a2b9b2ef76 100644 --- a/packages/fetch-extension/src/languages/en.json +++ b/packages/fetch-extension/src/languages/en.json @@ -339,7 +339,7 @@ "sign.list.message.wasm.button.close": "Close", "sign.list.message.wasm/MsgInstantiateContract.title": "Instantiate Wasm Contract", "sign.list.message.wasm/MsgInstantiateContract.content": "Instantiate code ID {codeId} contract with {admin} admin account and {label} label by funding {funds}", - "sign.list.message.wasm/MsgExecuteContract.title": "Execute Wasm Contract", + "sign.list.message.wasm/MsgExecuteContract.title": "Execute Contract", "sign.list.message.wasm/MsgExecuteContract.content": "Execute contract {address} by sending {sent}", "sign.list.message.wasm/MsgExecuteContract.content.badge.secret-wasm": "Encrypted", "sign.list.message.wasm/MsgExecuteContract.content.warning.secret-wasm.failed-decryption": "Failed to decrypt Secret message. This may be due to Fetch Wallet viewing key not matching the transaction viewing key.", diff --git a/packages/fetch-extension/src/pages/bridge/bridge-history.tsx b/packages/fetch-extension/src/pages/bridge/bridge-history.tsx index 984ff63015..202d47d36f 100644 --- a/packages/fetch-extension/src/pages/bridge/bridge-history.tsx +++ b/packages/fetch-extension/src/pages/bridge/bridge-history.tsx @@ -9,6 +9,7 @@ import { CoinPretty } from "@keplr-wallet/unit"; import { BridgeHistory } from "@keplr-wallet/stores"; import { Bech32Address } from "@keplr-wallet/cosmos"; import { Button } from "reactstrap"; +import restartIcon from "@assets/icon/undo.png"; export const proposalOptions = { ProposalActive: "PROPOSAL_STATUS_VOTING_PERIOD", @@ -46,6 +47,17 @@ export const BridgeHistoryView: FunctionComponent = observer(() => { navigate(-1); }} showBottomMenu={false} + rightRenderer={ + { + e.preventDefault(); + + bridgeHistory.fetch(); + }} + /> + } >

{bridgeHistory.isFetching ? ( diff --git a/packages/fetch-extension/src/pages/bridge/style.module.scss b/packages/fetch-extension/src/pages/bridge/style.module.scss index 6da3738c46..fee8c7bd5e 100644 --- a/packages/fetch-extension/src/pages/bridge/style.module.scss +++ b/packages/fetch-extension/src/pages/bridge/style.module.scss @@ -159,3 +159,9 @@ margin: -5px 0 0 15px; } } + +.refresh { + height: 20px; + margin: 22px; + cursor: pointer; +} diff --git a/packages/fetch-extension/src/pages/send/index.tsx b/packages/fetch-extension/src/pages/send/index.tsx index b6da166d08..e06cbfaf59 100644 --- a/packages/fetch-extension/src/pages/send/index.tsx +++ b/packages/fetch-extension/src/pages/send/index.tsx @@ -283,7 +283,6 @@ export const SendPage: FunctionComponent = observer(() => {
{ - debugger; e.preventDefault(); if (accountInfo.isReadyToSendMsgs && txStateIsValid) { diff --git a/packages/fetch-extension/src/pages/sign/adr-36.tsx b/packages/fetch-extension/src/pages/sign/adr-36.tsx index 5ad478ae45..145c0c9c9b 100644 --- a/packages/fetch-extension/src/pages/sign/adr-36.tsx +++ b/packages/fetch-extension/src/pages/sign/adr-36.tsx @@ -7,6 +7,9 @@ import { MsgRender } from "./details-tab"; import styleDetailsTab from "./details-tab.module.scss"; import { Label } from "reactstrap"; import { EthSignType } from "@keplr-wallet/types"; +import { renderEvmTxn } from "./evm"; +import { useIntl } from "react-intl"; +import { UnsignedTransaction } from "@ethersproject/transactions"; export const ADR36SignDocDetailsTab: FunctionComponent<{ signDocWrapper: SignDocWrapper; @@ -14,7 +17,9 @@ export const ADR36SignDocDetailsTab: FunctionComponent<{ ethSignType?: EthSignType; origin?: string; }> = observer(({ signDocWrapper, isADR36WithString, ethSignType, origin }) => { - const { chainStore } = useStore(); + const { chainStore, accountStore } = useStore(); + const intl = useIntl(); + const renderTitleText = () => { if (ethSignType && ethSignType === EthSignType.TRANSACTION) { return "Sign transaction for"; @@ -22,6 +27,8 @@ export const ADR36SignDocDetailsTab: FunctionComponent<{ return "Prove account ownership to"; }; + let evmRenderedMessage: React.ReactElement | undefined; + const signValue = useMemo(() => { if (signDocWrapper.aminoSignDoc.msgs.length !== 1) { throw new Error("Sign doc is inproper ADR-36"); @@ -44,6 +51,37 @@ export const ADR36SignDocDetailsTab: FunctionComponent<{ try { const decoder = new TextDecoder(); const str = decoder.decode(Buffer.from(msg.value.data, "base64")); + const txnParams: UnsignedTransaction = JSON.parse(str); + const msgContent = renderEvmTxn( + txnParams, + chainStore.current.feeCurrencies[0], + chainStore.current.currencies, + intl + ); + + evmRenderedMessage = ( + + + {msgContent.content} + +
+ {txnParams.data && + accountStore.getAccount(chainStore.current.chainId).isNanoLedger ? ( +
+
+ Before you click ‘Approve’ +
+
    +
  • + Connect your Ledger device and select the Ethereum app +
  • +
  • Enable ‘blind signing’ on your Ledger device
  • +
+
+ ) : null} +
+ ); + return JSON.stringify(JSON.parse(str), null, 2); } catch { return msg.value.data; @@ -51,52 +89,60 @@ export const ADR36SignDocDetailsTab: FunctionComponent<{ } else { return msg.value.data as string; } - }, [signDocWrapper.aminoSignDoc.msgs, isADR36WithString]); + }, [signDocWrapper.aminoSignDoc.msgs, isADR36WithString, ethSignType]); // TODO: Add warning view to let users turn on blind signing option on ledger if EIP712 return (
- - {origin ?? "Unknown"} - -
- -
-
-          {signValue}
-        
-
- -
-
{chainStore.current.chainName}
+ {evmRenderedMessage ? ( + evmRenderedMessage + ) : ( + + {origin ?? "Unknown"} + + )}
+ {!evmRenderedMessage && ( +
+ +
+
+              {signValue}
+            
+
+ +
+
{chainStore.current.chainName}
+
+
+ )}
); }); diff --git a/packages/fetch-extension/src/pages/sign/data-tab.tsx b/packages/fetch-extension/src/pages/sign/data-tab.tsx index 5ed92ac856..1cdc790bfb 100644 --- a/packages/fetch-extension/src/pages/sign/data-tab.tsx +++ b/packages/fetch-extension/src/pages/sign/data-tab.tsx @@ -1,15 +1,34 @@ -import React, { FunctionComponent } from "react"; +import React, { FunctionComponent, useMemo } from "react"; import { observer } from "mobx-react-lite"; import { SignDocHelper } from "@keplr-wallet/hooks"; import style from "./style.module.scss"; +import { EthSignType } from "@keplr-wallet/types"; export const DataTab: FunctionComponent<{ signDocHelper: SignDocHelper; -}> = observer(({ signDocHelper }) => { - return ( -
-      {JSON.stringify(signDocHelper.signDocJson, undefined, 2)}
-    
- ); + ethSignType?: EthSignType; +}> = observer(({ signDocHelper, ethSignType }) => { + const content = useMemo(() => { + if ( + ethSignType !== EthSignType.TRANSACTION || + !signDocHelper.signDocWrapper || + signDocHelper.signDocWrapper.aminoSignDoc.msgs.length !== 1 || + signDocHelper.signDocWrapper.aminoSignDoc.msgs[0].type !== + "sign/MsgSignData" + ) { + return JSON.stringify(signDocHelper.signDocJson, undefined, 2); + } + + const decoder = new TextDecoder(); + const jsonStr = decoder.decode( + Buffer.from( + signDocHelper.signDocWrapper.aminoSignDoc.msgs[0].value.data, + "base64" + ) + ); + return JSON.stringify(JSON.parse(jsonStr), undefined, 2); + }, [signDocHelper.signDocWrapper?.aminoSignDoc.msgs, ethSignType]); + + return
{content}
; }); diff --git a/packages/fetch-extension/src/pages/sign/evm.tsx b/packages/fetch-extension/src/pages/sign/evm.tsx new file mode 100644 index 0000000000..abb6f8b30e --- /dev/null +++ b/packages/fetch-extension/src/pages/sign/evm.tsx @@ -0,0 +1,90 @@ +/* eslint-disable react/display-name */ + +import { erc20MetadataInterface } from "@keplr-wallet/stores"; +import { Currency } from "@keplr-wallet/types"; +import { IntlShape } from "react-intl"; +import React from "react"; +import { + renderMsgEvmExecuteContract, + renderMsgSend, + renderUnknownMessage, +} from "./messages"; +import { UnsignedTransaction } from "@ethersproject/transactions"; +import { BigNumber } from "@ethersproject/bignumber"; +import { DenomHelper } from "@keplr-wallet/common"; + +export function renderEvmTxn( + txnParams: UnsignedTransaction, + nativeCurrency: Currency, + currencies: Currency[], + intl: IntlShape +): { + icon: string | undefined; + title: string; + content: React.ReactElement; +} { + try { + if ( + txnParams.value && + BigNumber.from(txnParams.value).gt(0) && + !txnParams.data + ) { + return renderMsgSend( + currencies, + intl, + [ + { + amount: BigNumber.from(txnParams.value).toString(), + denom: nativeCurrency.coinMinimalDenom, + }, + ], + txnParams.to ?? "", + true + ); + } + + if (txnParams.data) { + const sendCurrency = currencies.find((c) => { + const coin = new DenomHelper(c.coinMinimalDenom); + return coin.type === "erc20" && coin.contractAddress === txnParams.to; + }); + + if (sendCurrency) { + const erc20TransferParams = erc20MetadataInterface.parseTransaction({ + data: txnParams.data.toString(), + value: txnParams.value, + }); + + if (erc20TransferParams.name === "transfer") { + return renderMsgSend( + currencies, + intl, + [ + { + amount: erc20TransferParams.args["_value"].toString(), + denom: sendCurrency.coinMinimalDenom, + }, + ], + erc20TransferParams.args["_to"], + true + ); + } + } + + return renderMsgEvmExecuteContract( + intl, + txnParams.value && BigNumber.from(txnParams.value).gt(0) + ? { + amount: BigNumber.from(txnParams.value).toString(), + denom: nativeCurrency.coinMinimalDenom, + } + : undefined, + txnParams + ); + } + } catch (e) { + console.log(e); + } + + return renderUnknownMessage(txnParams); +} diff --git a/packages/fetch-extension/src/pages/sign/index.tsx b/packages/fetch-extension/src/pages/sign/index.tsx index 5ea1afd995..839bc22687 100644 --- a/packages/fetch-extension/src/pages/sign/index.tsx +++ b/packages/fetch-extension/src/pages/sign/index.tsx @@ -226,8 +226,6 @@ export const SignPage: FunctionComponent = observer(() => { return memoConfig.error != null || feeConfig.error != null; })(); - console.log("@@@@#!3", signDocHelper.signDocWrapper?.isADR36SignDoc); - return ( { })} > {tab === Tab.Data ? ( - + ) : null} {tab === Tab.Details ? ( signDocHelper.signDocWrapper?.isADR36SignDoc ? ( diff --git a/packages/fetch-extension/src/pages/sign/messages.tsx b/packages/fetch-extension/src/pages/sign/messages.tsx index 50fd4a53be..508b82cf6e 100644 --- a/packages/fetch-extension/src/pages/sign/messages.tsx +++ b/packages/fetch-extension/src/pages/sign/messages.tsx @@ -5,7 +5,7 @@ import { Bech32Address } from "@keplr-wallet/cosmos"; import { CoinUtils, Coin } from "@keplr-wallet/unit"; import { IntlShape, FormattedMessage, useIntl } from "react-intl"; import { Currency } from "@keplr-wallet/types"; -import { Button, Badge } from "reactstrap"; +import { Button, Badge, Label } from "reactstrap"; import { observer } from "mobx-react-lite"; import { useStore } from "../../stores"; import yaml from "js-yaml"; @@ -20,6 +20,7 @@ import { StakeAuthorization, } from "@keplr-wallet/proto-types/cosmos/staking/v1beta1/authz"; import { SendAuthorization } from "@keplr-wallet/proto-types/cosmos/bank/v1beta1/authz"; +import { UnsignedTransaction } from "@ethersproject/transactions"; export interface MessageObj { readonly type: string; @@ -180,7 +181,8 @@ export function renderMsgSend( currencies: Currency[], intl: IntlShape, amount: CoinPrimitive[], - toAddress: string + toAddress: string, + isEvm?: boolean ) { const receives: CoinPrimitive[] = []; for (const coinPrimitive of amount) { @@ -203,7 +205,7 @@ export function renderMsgSend( id="sign.list.message.cosmos-sdk/MsgSend.content" values={{ b: (...chunks: any[]) => {chunks}, - recipient: Bech32Address.shortenAddress(toAddress, 20), + recipient: Bech32Address.shortenAddress(toAddress, 20, isEvm), amount: receives .map((coin) => { return `${coin.amount} ${coin.denom}`; @@ -457,6 +459,50 @@ export function renderMsgInstantiateContract( }; } +export function renderMsgEvmExecuteContract( + intl: IntlShape, + sent: CoinPrimitive | undefined, + // eslint-disable-next-line @typescript-eslint/ban-types + txnParams: UnsignedTransaction +) { + return { + icon: "fas fa-cog", + title: intl.formatMessage({ + id: "sign.list.message.wasm/MsgExecuteContract.title", + }), + content: ( + + {chunks}, + br:
, + address: Bech32Address.shortenAddress(txnParams.to ?? "", 26, true), + ["only-sent-exist"]: (...chunks: any[]) => (sent ? chunks : ""), + sent: sent ? `${sent.amount} ${sent.denom}` : "", + }} + /> + +
+          {txnParams.data?.toString()}
+        
+
+ ), + }; +} + export function renderMsgExecuteContract( currencies: Currency[], intl: IntlShape, diff --git a/packages/fetch-extension/src/pages/sign/style.module.scss b/packages/fetch-extension/src/pages/sign/style.module.scss index e25c9e8e02..a27634d977 100644 --- a/packages/fetch-extension/src/pages/sign/style.module.scss +++ b/packages/fetch-extension/src/pages/sign/style.module.scss @@ -46,7 +46,6 @@ .tabContainer { height: 342px; overflow: auto; - flex: 1; display: flex; flex-direction: column; @@ -61,4 +60,6 @@ font-size: 12px; overflow: visible; background: transparent; + word-wrap: break-word; + white-space: pre-wrap; } From 96b4962396aec23b14b4b04ed18acb52c8b13ee1 Mon Sep 17 00:00:00 2001 From: agent-dominatrix <128386379+agent-dominatrix@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:20:19 +0530 Subject: [PATCH 5/5] fix: minor text updates --- packages/fetch-extension/src/pages/bridge/ethereum-bridge.tsx | 4 +--- packages/fetch-extension/src/pages/bridge/fetchhub-bridge.tsx | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/fetch-extension/src/pages/bridge/ethereum-bridge.tsx b/packages/fetch-extension/src/pages/bridge/ethereum-bridge.tsx index 767b799ec6..336475621f 100644 --- a/packages/fetch-extension/src/pages/bridge/ethereum-bridge.tsx +++ b/packages/fetch-extension/src/pages/bridge/ethereum-bridge.tsx @@ -153,9 +153,7 @@ export const Configure: FunctionComponent<{