Implement hooks for adding custom network to wallet

This commit is contained in:
Nabarun 2025-06-16 17:16:25 +05:30
parent cda6ebec30
commit 704a872eab
6 changed files with 138 additions and 15 deletions

View File

@ -0,0 +1,11 @@
{
"chainId": "laconic-testnet-2",
"networkName": "laconicd testnet-2",
"namespace": "cosmos",
"rpcUrl": "https://laconicd-sapo.laconic.com",
"blockExplorerUrl": "",
"nativeDenom": "alnt",
"addressPrefix": "laconic",
"coinType": 118,
"gasPrice": 0.001
}

View File

@ -6,6 +6,8 @@ import {
VITE_LACONICD_CHAIN_ID, VITE_LACONICD_CHAIN_ID,
VITE_WALLET_IFRAME_URL, VITE_WALLET_IFRAME_URL,
} from 'utils/constants'; } from 'utils/constants';
import { REQUEST_WALLET_ACCOUNTS, WALLET_ACCOUNTS_DATA } from '../../../constants';
import { useAddNetwork } from '../../../hooks/useAddNetwork';
const ApproveTransactionModal = ({ const ApproveTransactionModal = ({
setAccount, setAccount,
@ -16,11 +18,13 @@ const ApproveTransactionModal = ({
setIsDataReceived: (isReceived: boolean) => void; setIsDataReceived: (isReceived: boolean) => void;
isVisible: boolean; isVisible: boolean;
}) => { }) => {
const { setIframe, isNetworkAvailable } = useAddNetwork();
useEffect(() => { useEffect(() => {
const handleMessage = (event: MessageEvent) => { const handleMessage = (event: MessageEvent) => {
if (event.origin !== VITE_WALLET_IFRAME_URL) return; if (event.origin !== VITE_WALLET_IFRAME_URL) return;
if (event.data.type === 'WALLET_ACCOUNTS_DATA') { if (event.data.type === WALLET_ACCOUNTS_DATA) {
setIsDataReceived(true); setIsDataReceived(true);
if (event.data.data.length === 0) { if (event.data.data.length === 0) {
@ -53,13 +57,17 @@ const ApproveTransactionModal = ({
iframe.contentWindow.postMessage( iframe.contentWindow.postMessage(
{ {
type: 'REQUEST_WALLET_ACCOUNTS', type: REQUEST_WALLET_ACCOUNTS,
chainId: VITE_LACONICD_CHAIN_ID, chainId: VITE_LACONICD_CHAIN_ID,
}, },
VITE_WALLET_IFRAME_URL, VITE_WALLET_IFRAME_URL,
); );
}, []); }, []);
useEffect(() => {
getDataFromWallet()
}, [isNetworkAvailable, getDataFromWallet])
return ( return (
<Modal open={isVisible} disableEscapeKeyDown keepMounted> <Modal open={isVisible} disableEscapeKeyDown keepMounted>
<Box <Box
@ -80,7 +88,7 @@ const ApproveTransactionModal = ({
}} }}
> >
<iframe <iframe
onLoad={getDataFromWallet} onLoad={(event) => setIframe(event.target as HTMLIFrameElement)}
id="walletIframe" id="walletIframe"
src={`${VITE_WALLET_IFRAME_URL}/wallet-embed`} src={`${VITE_WALLET_IFRAME_URL}/wallet-embed`}
width="100%" width="100%"

View File

@ -1,9 +1,10 @@
import { useEffect, useState } from 'react'; import { useEffect } from 'react';
import { Modal } from '@mui/material'; import { Modal } from '@mui/material';
import { VITE_WALLET_IFRAME_URL } from 'utils/constants'; import { VITE_WALLET_IFRAME_URL } from 'utils/constants';
import useCheckBalance from '../../../hooks/useCheckBalance'; import useCheckBalance from '../../../hooks/useCheckBalance';
import { useAddNetwork } from '../../../hooks/useAddNetwork';
const CHECK_BALANCE_INTERVAL = 5000; const CHECK_BALANCE_INTERVAL = 5000;
const IFRAME_ID = 'checkBalanceIframe'; const IFRAME_ID = 'checkBalanceIframe';
@ -22,20 +23,19 @@ const CheckBalanceIframe = ({
IFRAME_ID, IFRAME_ID,
); );
const [isLoaded, setIsLoaded] = useState(false); const { isNetworkAvailable, setIframe } = useAddNetwork();
useEffect(() => { useEffect(() => {
if (!isLoaded) { if (!isNetworkAvailable || isBalanceSufficient) {
return; return;
} }
checkBalance(); checkBalance();
}, [amount, checkBalance, isLoaded]);
useEffect(() => { if (!isPollingEnabled) {
if (!isPollingEnabled || !isLoaded || isBalanceSufficient) {
return; return;
} }
const interval = setInterval(() => { const interval = setInterval(() => {
checkBalance(); checkBalance();
}, CHECK_BALANCE_INTERVAL); }, CHECK_BALANCE_INTERVAL);
@ -43,7 +43,7 @@ const CheckBalanceIframe = ({
return () => { return () => {
clearInterval(interval); clearInterval(interval);
}; };
}, [isBalanceSufficient, isPollingEnabled, checkBalance, isLoaded]); }, [isBalanceSufficient, isPollingEnabled, checkBalance, isNetworkAvailable]);
useEffect(() => { useEffect(() => {
onBalanceChange(isBalanceSufficient); onBalanceChange(isBalanceSufficient);
@ -52,7 +52,7 @@ const CheckBalanceIframe = ({
return ( return (
<Modal open={false} disableEscapeKeyDown keepMounted> <Modal open={false} disableEscapeKeyDown keepMounted>
<iframe <iframe
onLoad={() => setIsLoaded(true)} onLoad={(event) => setIframe(event.target as HTMLIFrameElement)}
id={IFRAME_ID} id={IFRAME_ID}
src={VITE_WALLET_IFRAME_URL} src={VITE_WALLET_IFRAME_URL}
width="100%" width="100%"

View File

@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom';
import { Box, Modal } from '@mui/material'; import { Box, Modal } from '@mui/material';
import { BASE_URL, VITE_WALLET_IFRAME_URL } from 'utils/constants'; import { BASE_URL, VITE_WALLET_IFRAME_URL } from 'utils/constants';
import { REQUEST_CREATE_OR_GET_ACCOUNTS, WALLET_ACCOUNTS_DATA } from '../../../constants';
const axiosInstance = axios.create({ const axiosInstance = axios.create({
baseURL: BASE_URL, baseURL: BASE_URL,
@ -91,7 +92,7 @@ const AutoSignInIFrameModal = () => {
const handleAccountsDataResponse = async (event: MessageEvent) => { const handleAccountsDataResponse = async (event: MessageEvent) => {
if (event.origin !== VITE_WALLET_IFRAME_URL) return; if (event.origin !== VITE_WALLET_IFRAME_URL) return;
if (event.data.type === 'WALLET_ACCOUNTS_DATA') { if (event.data.type === WALLET_ACCOUNTS_DATA) {
setAccountAddress(event.data.data[0]); setAccountAddress(event.data.data[0]);
} }
}; };
@ -115,7 +116,7 @@ const AutoSignInIFrameModal = () => {
iframe.contentWindow.postMessage( iframe.contentWindow.postMessage(
{ {
type: 'REQUEST_CREATE_OR_GET_ACCOUNTS', type: REQUEST_CREATE_OR_GET_ACCOUNTS,
chainId: '1', chainId: '1',
}, },
VITE_WALLET_IFRAME_URL, VITE_WALLET_IFRAME_URL,

View File

@ -3,3 +3,14 @@ export const SHORT_COMMIT_HASH_LENGTH = 8;
export const SERVER_GQL_PATH = 'graphql'; export const SERVER_GQL_PATH = 'graphql';
export const SHOPIFY_APP_URL = 'https://store.laconic.com'; export const SHOPIFY_APP_URL = 'https://store.laconic.com';
// iframe request types
export const REQUEST_CREATE_OR_GET_ACCOUNTS = 'REQUEST_CREATE_OR_GET_ACCOUNTS';
export const REQUEST_ADD_NETWORK = 'REQUEST_ADD_NETWORK';
export const REQUEST_WALLET_ACCOUNTS = 'REQUEST_WALLET_ACCOUNTS';
// iframe response types
export const WALLET_ACCOUNTS_DATA = 'WALLET_ACCOUNTS_DATA';
export const NETWORK_ADDED_RESPONSE = "NETWORK_ADDED_RESPONSE";
export const NETWORK_ALREADY_EXISTS_RESPONSE = "NETWORK_ALREADY_EXISTS_RESPONSE";
export const NETWORK_ADD_FAILED_RESPONSE = "NETWORK_ADD_FAILED_RESPONSE";

View File

@ -0,0 +1,92 @@
import { useState, useEffect } from 'react';
import { VITE_LACONICD_CHAIN_ID, VITE_WALLET_IFRAME_URL } from 'utils/constants';
import { NETWORK_ADD_FAILED_RESPONSE, NETWORK_ADDED_RESPONSE, NETWORK_ALREADY_EXISTS_RESPONSE, REQUEST_ADD_NETWORK } from '../constants';
interface NetworkData {
chainId: string;
namespace: string;
networkName: string;
rpcUrl: string;
coinType: string;
addressPrefix?: string;
blockExplorerUrl?: string;
nativeDenom?: string;
gasPrice?: string;
}
export const useAddNetwork = () => {
const [networkData, setNetworkData] = useState<NetworkData | null>(null);
const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);
const [isNetworkAvailable, setIsNetworkAvailable] = useState(false);
// useEffect to add network in embedded wallet
useEffect(() => {
if (!networkData) {
return;
}
if (!iframe?.contentWindow) {
console.error('iframe not loaded');
return;
}
iframe.contentWindow.postMessage(
{
type: REQUEST_ADD_NETWORK,
chainId: VITE_LACONICD_CHAIN_ID,
networkData,
},
VITE_WALLET_IFRAME_URL,
);
}, [networkData, iframe]);
// useEffect to listen for network add reponses
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== VITE_WALLET_IFRAME_URL) return;
switch (event.data.type) {
case NETWORK_ADDED_RESPONSE:
case NETWORK_ALREADY_EXISTS_RESPONSE:
// Once network is available, set state
setIsNetworkAvailable(true);
break;
case NETWORK_ADD_FAILED_RESPONSE:
setIsNetworkAvailable(false);
console.error("Network could not be added:", event.data.message);
break;
default:
break;
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, []);
useEffect(() => {
const loadNetworkData = async () => {
try {
const res = await fetch('/network.json');
const json = await res.json();
setNetworkData(json);
} catch (err) {
console.error('Failed to load network data:', err);
}
};
loadNetworkData();
}, []);
return {
networkData,
isNetworkAvailable,
setIframe
};
};