diff --git a/.env.example b/.env.example index d302927..d563968 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,8 @@ REACT_APP_WALLET_CONNECT_PROJECT_ID= + REACT_APP_DEFAULT_GAS_PRICE=0.025 # Reference: https://github.com/cosmos/cosmos-sdk/issues/16020 REACT_APP_GAS_ADJUSTMENT=2 REACT_APP_LACONICD_RPC_URL=https://laconicd-sapo.laconic.com -REACT_APP_AUTH_SECRET= + +REACT_APP_DEPLOY_APP_URL= diff --git a/src/App.tsx b/src/App.tsx index 0d812f2..b5d13fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,7 +24,7 @@ import SignRequest from "./screens/SignRequest"; import AddSession from "./screens/AddSession"; import WalletConnect from "./screens/WalletConnect"; import ApproveTransaction from "./screens/ApproveTransaction"; -import { Account, StackParamsList } from "./types"; +import { StackParamsList } from "./types"; import { EIP155_SIGNING_METHODS } from "./utils/wallet-connect/EIP155Data"; import { getSignParamsMessage } from "./utils/wallet-connect/helpers"; import ApproveTransfer from "./screens/ApproveTransfer"; @@ -39,7 +39,7 @@ import { Header } from "./components/Header"; import { WalletEmbed } from "./screens/WalletEmbed"; import { AutoSignIn } from "./screens/AutoSignIn"; import { checkSufficientFunds, getPathKey, sendMessage } from "./utils/misc"; -import { retrieveAccounts } from "./utils/accounts"; +import useAccountsData from "./hooks/useAccountsData"; const Stack = createStackNavigator(); @@ -49,29 +49,14 @@ const App = (): React.JSX.Element => { const { web3wallet, setActiveSessions } = useWalletConnect(); const { accounts, setCurrentIndex } = useAccounts(); const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks(); + const { getAccountsData } = useAccountsData(networksData); + const [modalVisible, setModalVisible] = useState(false); const [toastVisible, setToastVisible] = useState(false); const [currentProposal, setCurrentProposal] = useState< SignClientTypes.EventArguments["session_proposal"] | undefined >(); - const getAccountsData = useCallback(async (chainId: string): Promise => { - const targetNetwork = networksData.find(network => network.chainId === chainId); - - if (!targetNetwork) { - return []; - } - - const accounts = await retrieveAccounts(targetNetwork); - - if (!accounts || accounts.length === 0) { - return []; - } - - return accounts - }, [networksData]); - - const onSessionProposal = useCallback( async (proposal: SignClientTypes.EventArguments["session_proposal"]) => { if (!accounts.length || !accounts.length) { @@ -254,7 +239,11 @@ const App = (): React.JSX.Element => { const account = (await getAccountsData(chainId))[0]; if (!account) { - throw new Error('Account not found for the requested address.'); + throw new Error('Account not found'); + } + + if (network.namespace !== COSMOS) { + throw new Error('Unsupported network'); } const cosmosPrivKey = ( diff --git a/src/hooks/useAccountsData.ts b/src/hooks/useAccountsData.ts new file mode 100644 index 0000000..e30dd1c --- /dev/null +++ b/src/hooks/useAccountsData.ts @@ -0,0 +1,20 @@ +import { useCallback } from "react"; +import { retrieveAccounts } from "../utils/accounts"; +import { NetworksDataState } from "../types"; + +const useAccountsData = (networksData: NetworksDataState[]) => { + const getAccountsData = useCallback(async (chainId: string) => { + const targetNetwork = networksData.find(network => network.chainId === chainId); + + if (!targetNetwork) { + return []; + } + + const accounts = await retrieveAccounts(targetNetwork); + return accounts || []; + }, [networksData]); + + return { getAccountsData }; +}; + +export default useAccountsData; diff --git a/src/hooks/useGetOrCreateAccounts.ts b/src/hooks/useGetOrCreateAccounts.ts new file mode 100644 index 0000000..24a9a5b --- /dev/null +++ b/src/hooks/useGetOrCreateAccounts.ts @@ -0,0 +1,32 @@ +import { useEffect } from "react"; +import { createWallet } from "../utils/accounts"; +import { Account, NetworksDataState } from "../types"; +import { sendMessage } from "../utils/misc"; + +const useGetOrCreateAccounts = (networksData: NetworksDataState[], getAccountsData: (chainId: string) => Promise) => { + useEffect(() => { + const handleCreateAccounts = async (event: MessageEvent) => { + if (event.data.type !== 'REQUEST_CREATE_OR_GET_ACCOUNTS') return; + + let accountsData = await getAccountsData(event.data.chainId); + + if (accountsData.length === 0) { + console.log("Accounts not found, creating wallet..."); + await createWallet(networksData); + + // Re-fetch newly created accounts + accountsData = await getAccountsData(event.data.chainId); + } + + sendMessage(event.source as Window, 'WALLET_ACCOUNTS_DATA', accountsData, event.origin); + }; + + window.addEventListener('message', handleCreateAccounts); + + return () => { + window.removeEventListener('message', handleCreateAccounts); + }; + }, [networksData, getAccountsData ]); +}; + +export default useGetOrCreateAccounts; diff --git a/src/screens/AutoSignIn.tsx b/src/screens/AutoSignIn.tsx index 9d39675..10229d9 100644 --- a/src/screens/AutoSignIn.tsx +++ b/src/screens/AutoSignIn.tsx @@ -1,35 +1,26 @@ -import React, { useEffect, useCallback } from 'react'; +import React, { useEffect } from 'react'; -import { createWallet, retrieveAccounts } from '../utils/accounts'; import { useNetworks } from '../context/NetworksContext'; -import { Account } from '../types'; import { signMessage } from '../utils/sign-message'; import { EIP155 } from '../utils/constants'; import { sendMessage } from '../utils/misc'; +import useAccountsData from '../hooks/useAccountsData'; +import useGetOrCreateAccounts from '../hooks/useGetOrCreateAccounts'; export const AutoSignIn = () => { const { networksData } = useNetworks(); - const getAccountsData = useCallback(async (chainId: string): Promise => { - const targetNetwork = networksData.find(network => network.chainId === chainId); - - if (!targetNetwork) { - return []; - } - - const accounts = await retrieveAccounts(targetNetwork); - - if (!accounts || accounts.length === 0) { - return []; - } - - return accounts - }, [networksData]); + const { getAccountsData } = useAccountsData(networksData); useEffect(() => { const handleSignIn = async (event: MessageEvent) => { if (event.data.type !== 'AUTO_SIGN_IN') return; + if (event.origin !== process.env.REACT_APP_DEPLOY_APP_URL) { + console.log('Unauthorized app.'); + return; + } + const accountsData = await getAccountsData(event.data.chainId); if (!accountsData.length) { @@ -48,38 +39,8 @@ export const AutoSignIn = () => { }; }, [networksData, getAccountsData]); - useEffect(() => { - const getAccountAddress = async (event: MessageEvent) => { - if (event.data.type !== 'GET_ACCOUNT_ADDRESS') return; - - if (event.data.secret !== process.env.REACT_APP_AUTH_SECRET) { - console.log('Unauthorized app.'); - return; - } - - let accountsData = await getAccountsData(event.data.chainId); - - if (accountsData.length === 0) { - console.log("Accounts not found, creating wallet..."); - await createWallet(networksData); - - // Re-fetch newly created accounts - accountsData = await getAccountsData(event.data.chainId); - } - - if (!accountsData.length) { - return; - } - - sendMessage(event.source as Window, 'ACCOUNT_ADDRESS_RESPONSE', accountsData[0].address, event.origin); - }; - - window.addEventListener('message', getAccountAddress); - - return () => { - window.removeEventListener('message', getAccountAddress); - }; - }, [networksData, getAccountsData]); + // Custom hook for adding listener to get accounts data + useGetOrCreateAccounts(networksData, getAccountsData) return ( <> diff --git a/src/screens/WalletEmbed.tsx b/src/screens/WalletEmbed.tsx index 3185a6a..1a03596 100644 --- a/src/screens/WalletEmbed.tsx +++ b/src/screens/WalletEmbed.tsx @@ -15,7 +15,7 @@ import { SigningStargateClient, } from '@cosmjs/stargate'; -import { createWallet, retrieveAccounts, retrieveSingleAccount } from '../utils/accounts'; +import { retrieveAccounts, retrieveSingleAccount } from '../utils/accounts'; import AccountDetails from '../components/AccountDetails'; import styles from '../styles/stylesheet'; import DataBox from '../components/DataBox'; @@ -24,6 +24,7 @@ import { useNetworks } from '../context/NetworksContext'; import TxErrorDialog from '../components/TxErrorDialog'; import { MEMO } from '../screens/ApproveTransfer'; import { Account, NetworksDataState } from '../types'; +import useGetOrCreateAccounts from '../hooks/useGetOrCreateAccounts'; type TransactionDetails = { chainId: string; @@ -46,7 +47,7 @@ export const WalletEmbed = () => { const { networksData } = useNetworks(); - const getAccountsData = useCallback(async (chainId: string): Promise => { + const getAccountsData = useCallback(async (chainId: string): Promise => { const targetNetwork = networksData.find(network => network.chainId === chainId); if (!targetNetwork) { @@ -59,7 +60,7 @@ export const WalletEmbed = () => { return []; } - return accounts.map(account => account.address); + return accounts; }, [networksData]); useEffect(() => { @@ -83,29 +84,8 @@ export const WalletEmbed = () => { }; }, [getAccountsData]); - useEffect(() => { - const handleCreateAccounts = async (event: MessageEvent) => { - if (event.data.type !== 'REQUEST_CREATE_OR_GET_ACCOUNTS') return; - - let accountsData = await getAccountsData(event.data.chainId); - - if (accountsData.length === 0) { - console.log("Accounts not found, creating wallet..."); - await createWallet(networksData); - - // Re-fetch newly created accounts - accountsData = await getAccountsData(event.data.chainId); - } - - sendMessage(event.source as Window, 'WALLET_ACCOUNTS_DATA', accountsData, event.origin); - }; - - window.addEventListener('message', handleCreateAccounts); - - return () => { - window.removeEventListener('message', handleCreateAccounts); - }; - }, [networksData, getAccountsData]); + // Custom hook for adding listener to get accounts data + useGetOrCreateAccounts(networksData, getAccountsData); const handleTxRequested = useCallback( async (event: MessageEvent) => { @@ -168,7 +148,7 @@ export const WalletEmbed = () => { }); if (!checkSufficientFunds(amount, balance.amount)) { - console.log("Insufficient funds detected"); + console.log("Insufficient funds detected. Throwing error."); throw new Error('Insufficient funds'); } diff --git a/stack/stack-orchestrator/compose/docker-compose-laconic-wallet-web.yml b/stack/stack-orchestrator/compose/docker-compose-laconic-wallet-web.yml index 9c84fe3..9ae5335 100644 --- a/stack/stack-orchestrator/compose/docker-compose-laconic-wallet-web.yml +++ b/stack/stack-orchestrator/compose/docker-compose-laconic-wallet-web.yml @@ -10,7 +10,7 @@ services: CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE:-0.025} CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT:-2} CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL:-https://laconicd.laconic.com} - CERC_AUTH_SECRET: ${CERC_AUTH_SECRET} + CERC_DEPLOY_APP_URL: ${CERC_DEPLOY_APP_URL} command: ["bash", "/scripts/run.sh"] volumes: - ../config/app/run.sh:/scripts/run.sh diff --git a/stack/stack-orchestrator/config/app/run.sh b/stack/stack-orchestrator/config/app/run.sh index 031963c..5cdfcfa 100755 --- a/stack/stack-orchestrator/config/app/run.sh +++ b/stack/stack-orchestrator/config/app/run.sh @@ -10,14 +10,14 @@ echo "WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}" echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}" echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}" echo "CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL}" -echo "CERC_AUTH_SECRET: ${CERC_AUTH_SECRET}" +echo "CERC_DEPLOY_APP_URL: ${CERC_DEPLOY_APP_URL}" # Build with required env REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_ID \ REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE \ REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT \ REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL \ -REACT_APP_AUTH_SECRET=$CERC_AUTH_SECRET \ +REACT_APP_DEPLOY_APP_URL=$CERC_DEPLOY_APP_URL \ yarn build # Define the directory and file path diff --git a/stack/stack-orchestrator/stack/laconic-wallet-web/README.md b/stack/stack-orchestrator/stack/laconic-wallet-web/README.md index 75e789f..667e713 100644 --- a/stack/stack-orchestrator/stack/laconic-wallet-web/README.md +++ b/stack/stack-orchestrator/stack/laconic-wallet-web/README.md @@ -64,8 +64,8 @@ Instructions for running the `laconic-wallet-web` using [laconic-so](https://git # RPC endpoint of laconicd node (default: https://laconicd.laconic.com) CERC_LACONICD_RPC_URL= - # Secret to protect auto sign-in route (should match the secret set in deploy app) - REACT_APP_AUTH_SECRET= + # Deploy app URL used for checking origin of the messages for auto-sign-in route + REACT_APP_DEPLOY_APP_URL= ``` ## Start the deployment