forked from cerc-io/snowballtools-base
Implement feature to add custom network config to embedded wallet (#59)
Part of https://www.notion.so/Laconic-Mainnet-Plan-1eca6b22d47280569cd0d1e6d711d949 Co-authored-by: Shreerang Kale <shreerangkale@gmail.com> Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com> Reviewed-on: cerc-io/snowballtools-base#59 Co-authored-by: Nabarun <nabarun@deepstacksoft.com> Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
parent
cda6ebec30
commit
dd1d747b60
@ -47,11 +47,11 @@ jobs:
|
||||
cat > packages/deployer/config.yml <<EOF
|
||||
services:
|
||||
registry:
|
||||
rpcEndpoint: https://laconicd-sapo.laconic.com
|
||||
gqlEndpoint: https://laconicd-sapo.laconic.com/api
|
||||
rpcEndpoint: https://laconicd-mainnet-1.laconic.com
|
||||
gqlEndpoint: https://laconicd-mainnet-1.laconic.com/api
|
||||
userKey: $REGISTRY_USER_KEY
|
||||
bondId: $REGISTRY_BOND_ID
|
||||
chainId: laconic-testnet-2
|
||||
chainId: laconic-mainnet
|
||||
gasPrice: 0.001alnt
|
||||
EOF
|
||||
|
||||
|
@ -15,7 +15,6 @@ VITE_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_github_clientid'
|
||||
VITE_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_pwa_templaterepo'
|
||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo'
|
||||
VITE_GITHUB_NEXT_APP_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_next_app_templaterepo'
|
||||
VITE_LACONICD_CHAIN_ID = 'LACONIC_HOSTED_CONFIG_laconicd_chain_id'
|
||||
VITE_WALLET_IFRAME_URL = 'LACONIC_HOSTED_CONFIG_wallet_iframe_url'
|
||||
VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
|
||||
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
|
||||
|
@ -14,5 +14,6 @@
|
||||
"prepare": "husky install",
|
||||
"build": "lerna run build --stream",
|
||||
"lint": "lerna run lint --stream"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
services:
|
||||
registry:
|
||||
rpcEndpoint: https://laconicd-sapo.laconic.com
|
||||
gqlEndpoint: https://laconicd-sapo.laconic.com/api
|
||||
rpcEndpoint: https://laconicd-mainnet-1.laconic.com
|
||||
gqlEndpoint: https://laconicd-mainnet-1.laconic.com/api
|
||||
userKey:
|
||||
bondId:
|
||||
chainId: laconic-testnet-2
|
||||
chainId: laconic-mainnet
|
||||
gasPrice: 0.001alnt
|
||||
|
@ -133,7 +133,6 @@ record:
|
||||
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
|
||||
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
|
||||
LACONIC_HOSTED_CONFIG_wallet_iframe_url: https://wallet.laconic.com
|
||||
meta:
|
||||
note: Added @ $CURRENT_DATE_TIME
|
||||
|
@ -12,5 +12,4 @@ VITE_BUGSNAG_API_KEY=
|
||||
VITE_PASSKEY_WALLET_RPID=
|
||||
VITE_TURNKEY_API_BASE_URL=
|
||||
|
||||
VITE_LACONICD_CHAIN_ID=
|
||||
VITE_WALLET_IFRAME_URL=
|
||||
|
BIN
packages/frontend/public/android-chrome-192x192.png
Normal file
BIN
packages/frontend/public/android-chrome-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
11
packages/frontend/public/network.json
Normal file
11
packages/frontend/public/network.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"chainId": "laconic-mainnet",
|
||||
"networkName": "laconicd mainnet",
|
||||
"namespace": "cosmos",
|
||||
"rpcUrl": "https://laconicd-mainnet-1.laconic.com",
|
||||
"blockExplorerUrl": "",
|
||||
"nativeDenom": "alnt",
|
||||
"addressPrefix": "laconic",
|
||||
"coinType": 118,
|
||||
"gasPrice": 0.001
|
||||
}
|
@ -3,9 +3,10 @@ import { useCallback, useEffect } from 'react';
|
||||
import { Box, Modal } from '@mui/material';
|
||||
|
||||
import {
|
||||
VITE_LACONICD_CHAIN_ID,
|
||||
VITE_WALLET_IFRAME_URL,
|
||||
} from 'utils/constants';
|
||||
import { REQUEST_WALLET_ACCOUNTS, WALLET_ACCOUNTS_DATA } from '../../../constants';
|
||||
import { useAddNetwork } from '../../../hooks/useAddNetwork';
|
||||
|
||||
const ApproveTransactionModal = ({
|
||||
setAccount,
|
||||
@ -16,15 +17,17 @@ const ApproveTransactionModal = ({
|
||||
setIsDataReceived: (isReceived: boolean) => void;
|
||||
isVisible: boolean;
|
||||
}) => {
|
||||
const { setIframe, isNetworkAvailable, networkData } = useAddNetwork();
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.origin !== VITE_WALLET_IFRAME_URL) return;
|
||||
|
||||
if (event.data.type === 'WALLET_ACCOUNTS_DATA') {
|
||||
if (event.data.type === WALLET_ACCOUNTS_DATA) {
|
||||
setIsDataReceived(true);
|
||||
|
||||
if (event.data.data.length === 0) {
|
||||
console.error(`Accounts not present for chainId: ${VITE_LACONICD_CHAIN_ID}`);
|
||||
console.error(`Accounts not present for chainId: ${networkData?.chainId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -41,9 +44,14 @@ const ApproveTransactionModal = ({
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
};
|
||||
}, []);
|
||||
}, [networkData]);
|
||||
|
||||
const getDataFromWallet = useCallback(() => {
|
||||
if (!networkData) {
|
||||
console.error('networkData should not be empty');
|
||||
return;
|
||||
}
|
||||
|
||||
const iframe = document.getElementById('walletIframe') as HTMLIFrameElement;
|
||||
|
||||
if (!iframe.contentWindow) {
|
||||
@ -53,12 +61,18 @@ const ApproveTransactionModal = ({
|
||||
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
type: 'REQUEST_WALLET_ACCOUNTS',
|
||||
chainId: VITE_LACONICD_CHAIN_ID,
|
||||
type: REQUEST_WALLET_ACCOUNTS,
|
||||
chainId: networkData.chainId,
|
||||
},
|
||||
VITE_WALLET_IFRAME_URL,
|
||||
);
|
||||
}, []);
|
||||
}, [networkData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNetworkAvailable) {
|
||||
getDataFromWallet();
|
||||
}
|
||||
}, [isNetworkAvailable, getDataFromWallet])
|
||||
|
||||
return (
|
||||
<Modal open={isVisible} disableEscapeKeyDown keepMounted>
|
||||
@ -80,7 +94,7 @@ const ApproveTransactionModal = ({
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
onLoad={getDataFromWallet}
|
||||
onLoad={(event) => setIframe(event.target as HTMLIFrameElement)}
|
||||
id="walletIframe"
|
||||
src={`${VITE_WALLET_IFRAME_URL}/wallet-embed`}
|
||||
width="100%"
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { Modal } from '@mui/material';
|
||||
|
||||
import { VITE_WALLET_IFRAME_URL } from 'utils/constants';
|
||||
import useCheckBalance from '../../../hooks/useCheckBalance';
|
||||
import { useAddNetwork } from '../../../hooks/useAddNetwork';
|
||||
|
||||
const CHECK_BALANCE_INTERVAL = 5000;
|
||||
const IFRAME_ID = 'checkBalanceIframe';
|
||||
@ -22,20 +23,19 @@ const CheckBalanceIframe = ({
|
||||
IFRAME_ID,
|
||||
);
|
||||
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
const { isNetworkAvailable, setIframe } = useAddNetwork();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoaded) {
|
||||
if (!isNetworkAvailable || isBalanceSufficient) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkBalance();
|
||||
}, [amount, checkBalance, isLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPollingEnabled || !isLoaded || isBalanceSufficient) {
|
||||
if (!isPollingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const interval = setInterval(() => {
|
||||
checkBalance();
|
||||
}, CHECK_BALANCE_INTERVAL);
|
||||
@ -43,7 +43,7 @@ const CheckBalanceIframe = ({
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [isBalanceSufficient, isPollingEnabled, checkBalance, isLoaded]);
|
||||
}, [isBalanceSufficient, isPollingEnabled, checkBalance, isNetworkAvailable]);
|
||||
|
||||
useEffect(() => {
|
||||
onBalanceChange(isBalanceSufficient);
|
||||
@ -52,7 +52,7 @@ const CheckBalanceIframe = ({
|
||||
return (
|
||||
<Modal open={false} disableEscapeKeyDown keepMounted>
|
||||
<iframe
|
||||
onLoad={() => setIsLoaded(true)}
|
||||
onLoad={(event) => setIframe(event.target as HTMLIFrameElement)}
|
||||
id={IFRAME_ID}
|
||||
src={VITE_WALLET_IFRAME_URL}
|
||||
width="100%"
|
||||
|
@ -25,10 +25,10 @@ import ApproveTransactionModal from './ApproveTransactionModal';
|
||||
import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm';
|
||||
import { EnvironmentVariablesFormValues } from 'types/types';
|
||||
import {
|
||||
VITE_LACONICD_CHAIN_ID,
|
||||
VITE_WALLET_IFRAME_URL,
|
||||
} from 'utils/constants';
|
||||
import CheckBalanceIframe from './CheckBalanceIframe';
|
||||
import { useAddNetwork } from '../../../hooks/useAddNetwork';
|
||||
|
||||
type ConfigureDeploymentFormValues = {
|
||||
option: string;
|
||||
@ -71,6 +71,7 @@ const Configure = () => {
|
||||
const navigate = useNavigate();
|
||||
const { toast, dismiss } = useToast();
|
||||
const client = useGQLClient();
|
||||
const { networkData } = useAddNetwork()
|
||||
|
||||
const methods = useForm<ConfigureFormValues>({
|
||||
defaultValues: {
|
||||
@ -421,7 +422,7 @@ const Configure = () => {
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
type: 'REQUEST_TX',
|
||||
chainId: VITE_LACONICD_CHAIN_ID,
|
||||
chainId: networkData?.chainId,
|
||||
fromAddress: sender,
|
||||
toAddress: recipient,
|
||||
amount,
|
||||
|
@ -43,6 +43,11 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
||||
|
||||
const checkAuctionStatus = useCallback(async () => {
|
||||
const result = await client.getAuctionData(project.auctionId);
|
||||
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
|
||||
setAuctionStatus(result.status);
|
||||
setAuctionDetails(result);
|
||||
}, [project.auctionId, project.deployers, project.fundsReleased]);
|
||||
|
@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Box, Modal } from '@mui/material';
|
||||
|
||||
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({
|
||||
baseURL: BASE_URL,
|
||||
@ -91,7 +92,7 @@ const AutoSignInIFrameModal = () => {
|
||||
const handleAccountsDataResponse = async (event: MessageEvent) => {
|
||||
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]);
|
||||
}
|
||||
};
|
||||
@ -115,7 +116,7 @@ const AutoSignInIFrameModal = () => {
|
||||
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
type: 'REQUEST_CREATE_OR_GET_ACCOUNTS',
|
||||
type: REQUEST_CREATE_OR_GET_ACCOUNTS,
|
||||
chainId: '1',
|
||||
},
|
||||
VITE_WALLET_IFRAME_URL,
|
||||
|
@ -3,3 +3,14 @@ export const SHORT_COMMIT_HASH_LENGTH = 8;
|
||||
export const SERVER_GQL_PATH = 'graphql';
|
||||
|
||||
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";
|
||||
|
92
packages/frontend/src/hooks/useAddNetwork.ts
Normal file
92
packages/frontend/src/hooks/useAddNetwork.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
type: REQUEST_ADD_NETWORK,
|
||||
chainId: networkData.chainId,
|
||||
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,
|
||||
iframe,
|
||||
setIframe
|
||||
};
|
||||
};
|
@ -1,11 +1,17 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
import { VITE_LACONICD_CHAIN_ID } from 'utils/constants';
|
||||
import { useAddNetwork } from './useAddNetwork';
|
||||
|
||||
const useCheckBalance = (amount: string, iframeId: string) => {
|
||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>();
|
||||
|
||||
const { networkData } = useAddNetwork()
|
||||
|
||||
const checkBalance = useCallback(() => {
|
||||
if (!networkData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const iframe = document.getElementById(iframeId) as HTMLIFrameElement;
|
||||
|
||||
if (!iframe || !iframe.contentWindow) {
|
||||
@ -16,12 +22,12 @@ const useCheckBalance = (amount: string, iframeId: string) => {
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
type: 'CHECK_BALANCE',
|
||||
chainId: VITE_LACONICD_CHAIN_ID,
|
||||
chainId: networkData.chainId,
|
||||
amount,
|
||||
},
|
||||
import.meta.env.VITE_WALLET_IFRAME_URL
|
||||
);
|
||||
}, [iframeId, amount]);
|
||||
}, [iframeId, amount, networkData]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
|
@ -204,7 +204,7 @@ const OverviewTabPanel = () => {
|
||||
{project.deployments &&
|
||||
project.deployments.length > 0 &&
|
||||
project.deployments.map((deployment) => (
|
||||
<div className="flex gap-2 items-center">
|
||||
<div key={deployment.id} className="flex gap-2 items-center">
|
||||
<Link to={deployment.applicationDeploymentRecordData.url}>
|
||||
<span className="text-controls-primary dark:text-foreground group hover:border-controls-primary transition-colors border-b border-b-transparent flex gap-2 items-center text-sm tracking-tight">
|
||||
{deployment.applicationDeploymentRecordData.url}
|
||||
|
@ -10,5 +10,4 @@ export const VITE_GITHUB_NEXT_APP_TEMPLATE_REPO = import.meta.env
|
||||
export const VITE_GITHUB_CLIENT_ID = import.meta.env.VITE_GITHUB_CLIENT_ID;
|
||||
export const VITE_BUGSNAG_API_KEY = import.meta.env.VITE_BUGSNAG_API_KEY;
|
||||
export const VITE_LIT_RELAY_API_KEY = import.meta.env.VITE_LIT_RELAY_API_KEY;
|
||||
export const VITE_LACONICD_CHAIN_ID = import.meta.env.VITE_LACONICD_CHAIN_ID;
|
||||
export const VITE_WALLET_IFRAME_URL = import.meta.env.VITE_WALLET_IFRAME_URL;
|
||||
|
@ -414,14 +414,25 @@ export class GQLClient {
|
||||
return data;
|
||||
}
|
||||
|
||||
async getAuctionData(auctionId: string): Promise<types.Auction> {
|
||||
const { data } = await this.client.query({
|
||||
async getAuctionData(auctionId: string): Promise<types.Auction | null> {
|
||||
const { data, errors } = await this.client.query({
|
||||
query: queries.getAuctionData,
|
||||
variables: {
|
||||
auctionId,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (errors && errors.length) {
|
||||
const isAuctionNotFound = errors.some((error) =>
|
||||
error.message?.includes('Auction not found')
|
||||
);
|
||||
|
||||
if (isAuctionNotFound) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return data.getAuctionData;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user