Compare commits

..

1 Commits

Author SHA1 Message Date
Shreerang Kale
2edfb4b4cf Format public key in create validator tx body 2025-06-06 12:32:33 +05:30
12 changed files with 131 additions and 220 deletions

View File

@ -5,5 +5,7 @@ REACT_APP_DEFAULT_GAS_PRICE=0.025
REACT_APP_GAS_ADJUSTMENT=2 REACT_APP_GAS_ADJUSTMENT=2
REACT_APP_LACONICD_RPC_URL=https://laconicd-sapo.laconic.com REACT_APP_LACONICD_RPC_URL=https://laconicd-sapo.laconic.com
REACT_APP_ZENITHD_RPC_URL=
# Example: https://example-url-1.com,https://example-url-2.com # Example: https://example-url-1.com,https://example-url-2.com
REACT_APP_ALLOWED_URLS= REACT_APP_ALLOWED_URLS=

View File

@ -1,6 +1,6 @@
{ {
"name": "web-wallet", "name": "web-wallet",
"version": "0.1.7", "version": "0.1.6",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@cerc-io/registry-sdk": "^0.2.5", "@cerc-io/registry-sdk": "^0.2.5",

View File

@ -45,7 +45,6 @@ import SignRequestEmbed from "./screens/SignRequestEmbed";
import useAddAccountEmbed from "./hooks/useAddAccountEmbed"; import useAddAccountEmbed from "./hooks/useAddAccountEmbed";
import useExportPKEmbed from "./hooks/useExportPrivateKeyEmbed"; import useExportPKEmbed from "./hooks/useExportPrivateKeyEmbed";
import { SignTxEmbed } from "./screens/SignTxEmbed"; import { SignTxEmbed } from "./screens/SignTxEmbed";
import useCreateNetwork from "./hooks/useCreateNetwork";
const Stack = createStackNavigator<StackParamsList>(); const Stack = createStackNavigator<StackParamsList>();
@ -288,7 +287,6 @@ const App = (): React.JSX.Element => {
useWebViewHandler(); useWebViewHandler();
useAddAccountEmbed(); useAddAccountEmbed();
useExportPKEmbed(); useExportPKEmbed();
useCreateNetwork();
return ( return (
<Surface style={styles.appSurface}> <Surface style={styles.appSurface}>

View File

@ -1,99 +0,0 @@
import { useEffect, useCallback } from "react";
import { addNewNetwork, createWallet, checkNetworkForChainID, isWalletCreated } from "../utils/accounts";
import { useNetworks } from "../context/NetworksContext";
import { NETWORK_ADDED_RESPONSE, NETWORK_ADD_FAILED_RESPONSE, NETWORK_ALREADY_EXISTS_RESPONSE, REQUEST_ADD_NETWORK } from "../utils/constants";
import { NetworksFormData } from "../types";
import { sendMessage } from "../utils/misc";
const REACT_APP_ALLOWED_URLS = import.meta.env.REACT_APP_ALLOWED_URLS;
const useCreateNetwork = () => {
const { networksData, setNetworksData } = useNetworks();
const getOrCreateNetwork = useCallback(
async (chainId: string, networkData: NetworksFormData, sourceOrigin?: string) => {
if (!sourceOrigin) {
return;
}
try {
const isCreated = await isWalletCreated();
if (!isCreated) {
console.log("Wallet not found, creating wallet...");
await createWallet(networksData);
}
const isNetworkPresent = await checkNetworkForChainID(chainId);
if (!isNetworkPresent) {
console.log("ChainId not found. Adding network");
const resolvedNetworkData = {
chainId,
namespace: networkData.namespace,
networkName: networkData.networkName,
rpcUrl: networkData.rpcUrl,
blockExplorerUrl: networkData.blockExplorerUrl || "",
addressPrefix: networkData.addressPrefix || "",
coinType: networkData.coinType,
nativeDenom: networkData.nativeDenom || "",
gasPrice: networkData.gasPrice || String(import.meta.env.REACT_APP_DEFAULT_GAS_PRICE),
currencySymbol: networkData.currencySymbol || "",
isDefault: false,
};
const retrievedNetworksData = await addNewNetwork(resolvedNetworkData);
setNetworksData(retrievedNetworksData);
sendMessage(window.parent, NETWORK_ADDED_RESPONSE, {
type: NETWORK_ADDED_RESPONSE,
chainId
}, sourceOrigin);
} else {
sendMessage(window.parent, NETWORK_ALREADY_EXISTS_RESPONSE, {
type: NETWORK_ALREADY_EXISTS_RESPONSE,
chainId
}, sourceOrigin);
}
} catch (error) {
console.error("Error in getOrCreateNetwork:", error);
sendMessage(window.parent, NETWORK_ADD_FAILED_RESPONSE, {
type: NETWORK_ADD_FAILED_RESPONSE,
message: error instanceof Error ? error.message : "Unknown error"
}, sourceOrigin);
}
},
[networksData, setNetworksData]
);
useEffect(() => {
const handleCreateNetwork = async (event: MessageEvent) => {
if (event.data.type !== REQUEST_ADD_NETWORK) return;
if (!REACT_APP_ALLOWED_URLS) {
console.log("Allowed URLs are not set");
return;
}
const allowedUrls = REACT_APP_ALLOWED_URLS.split(",").map(url => url.trim());
if (!allowedUrls.includes(event.origin)) {
console.log("Unauthorized app.");
return;
}
const { chainId, networkData } = event.data;
await getOrCreateNetwork(chainId, networkData, event.origin);
};
window.addEventListener("message", handleCreateNetwork);
return () => {
window.removeEventListener("message", handleCreateNetwork);
};
}, [getOrCreateNetwork]);
};
export default useCreateNetwork;

View File

@ -1,6 +1,6 @@
import { useEffect, useCallback } from "react"; import { useEffect, useCallback } from "react";
import { createWallet, isWalletCreated } from "../utils/accounts"; import { createWallet } from "../utils/accounts";
import { sendMessage } from "../utils/misc"; import { sendMessage } from "../utils/misc";
import useAccountsData from "./useAccountsData"; import useAccountsData from "./useAccountsData";
import { useNetworks } from "../context/NetworksContext"; import { useNetworks } from "../context/NetworksContext";
@ -16,15 +16,14 @@ const useGetOrCreateAccounts = () => {
// Wrap the function in useCallback to prevent recreation on each render // Wrap the function in useCallback to prevent recreation on each render
const getOrCreateAccountsForChain = useCallback(async (chainId: string) => { const getOrCreateAccountsForChain = useCallback(async (chainId: string) => {
const isCreated = await isWalletCreated(); let accountsData = await getAccountsData(chainId);
if (!isCreated) { if (accountsData.length === 0) {
console.log("Accounts not found, creating wallet..."); console.log("Accounts not found, creating wallet...");
await createWallet(networksData); await createWallet(networksData);
accountsData = await getAccountsData(chainId);
} }
const accountsData = await getAccountsData(chainId);
// Update the AccountsContext with the new accounts // Update the AccountsContext with the new accounts
setAccounts(accountsData); setAccounts(accountsData);

View File

@ -5,6 +5,7 @@ interface ImportMetaEnv {
readonly REACT_APP_DEFAULT_GAS_PRICE: string; readonly REACT_APP_DEFAULT_GAS_PRICE: string;
readonly REACT_APP_GAS_ADJUSTMENT: string; readonly REACT_APP_GAS_ADJUSTMENT: string;
readonly REACT_APP_LACONICD_RPC_URL: string; readonly REACT_APP_LACONICD_RPC_URL: string;
readonly REACT_APP_ZENITHD_RPC_URL: string;
readonly REACT_APP_ALLOWED_URLS: string; readonly REACT_APP_ALLOWED_URLS: string;
} }

View File

@ -1,6 +1,8 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form"; import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form";
import { TextInput, HelperText } from "react-native-paper"; import { TextInput, HelperText } from "react-native-paper";
import { HDNode } from "ethers/lib/utils";
import { chains } from "chain-registry"; import { chains } from "chain-registry";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import { z } from "zod"; import { z } from "zod";
@ -8,21 +10,27 @@ import { z } from "zod";
import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Divider, Grid } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { StackParamsList } from "../types"; import { StackParamsList } from "../types";
import { SelectNetworkType } from "../components/SelectNetworkType"; import { SelectNetworkType } from "../components/SelectNetworkType";
import { addNewNetwork } from "../utils/accounts"; import { addAccountsForNetwork, getNextAccountId, storeNetworkData } from "../utils/accounts";
import { useNetworks } from "../context/NetworksContext"; import { useNetworks } from "../context/NetworksContext";
import { import {
COSMOS,
EIP155, EIP155,
CHAINID_DEBOUNCE_DELAY, CHAINID_DEBOUNCE_DELAY,
EMPTY_FIELD_ERROR, EMPTY_FIELD_ERROR,
INVALID_URL_ERROR, INVALID_URL_ERROR,
IS_NUMBER_REGEX, IS_NUMBER_REGEX,
} from "../utils/constants"; } from "../utils/constants";
import { getCosmosAccountByHDPath } from "../utils/accounts";
import ETH_CHAINS from "../assets/ethereum-chains.json"; import ETH_CHAINS from "../assets/ethereum-chains.json";
import {
getInternetCredentials,
setInternetCredentials,
} from "../utils/key-store";
import { Divider, Grid } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { Layout } from "../components/Layout"; import { Layout } from "../components/Layout";
const ethNetworkDataSchema = z.object({ const ethNetworkDataSchema = z.object({
@ -135,10 +143,73 @@ const AddNetwork = () => {
isDefault: false, isDefault: false,
}; };
const retrievedNetworksData = await addNewNetwork(newNetworkData); const mnemonicServer = await getInternetCredentials("mnemonicServer");
const mnemonic = mnemonicServer;
if (!mnemonic) {
throw new Error("Mnemonic not found");
}
const hdNode = HDNode.fromMnemonic(mnemonic);
const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`;
const node = hdNode.derivePath(hdPath);
let address;
switch (newNetworkData.namespace) {
case EIP155:
address = node.address;
break;
case COSMOS:
address = (
await getCosmosAccountByHDPath(
mnemonic,
hdPath,
(newNetworkData as z.infer<typeof cosmosNetworkDataSchema>)
.addressPrefix,
)
).data.address;
break;
default:
throw new Error("Unsupported namespace");
}
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
const retrievedNetworksData = await storeNetworkData(newNetworkData);
setNetworksData(retrievedNetworksData); setNetworksData(retrievedNetworksData);
// Get number of accounts in first network
const nextAccountId = await getNextAccountId(
`${retrievedNetworksData[0].namespace}:${retrievedNetworksData[0].chainId}`,
);
const selectedNetwork = retrievedNetworksData.find(
(network) => network.chainId === newNetworkData.chainId,
);
await Promise.all([
setInternetCredentials(
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
"_",
accountInfo,
),
setInternetCredentials(
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
"_",
"1",
),
setInternetCredentials(
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
"_",
"0",
),
]);
await addAccountsForNetwork(selectedNetwork!, nextAccountId - 1);
navigation.navigate("Home"); navigation.navigate("Home");
}, },
[navigation, namespace, setNetworksData], [navigation, namespace, setNetworksData],

View File

@ -277,7 +277,26 @@ export const SignTxEmbed = () => {
return return
} }
return JSONbig.stringify(transactionDetails.txBody, null, 2) let txBody = transactionDetails.txBody;
// If it's a MsgCreateValidator, format pubkey in present in the message
if (txBody.value.messages[0].typeUrl === "/cosmos.staking.v1beta1.MsgCreateValidator") {
txBody = {
...txBody,
value: {
...txBody.value,
messages: txBody.value.messages.map((msg) => ({
...msg,
value: {
...msg.value,
pubkey: decodeOptionalPubkey(msg.value.pubkey),
},
})),
},
}
}
return JSONbig.stringify(txBody, null, 2)
}, },
[transactionDetails] [transactionDetails]
); );

View File

@ -64,7 +64,7 @@ export type NetworksFormData = {
namespace: string; namespace: string;
nativeDenom?: string; nativeDenom?: string;
addressPrefix?: string; addressPrefix?: string;
coinType: string; coinType?: string;
gasPrice?: string; gasPrice?: string;
isDefault: boolean; isDefault: boolean;
}; };

View File

@ -160,77 +160,6 @@ const addAccountFromHDPath = async (
} }
}; };
const addNewNetwork = async (
newNetworkData: NetworksFormData
): Promise<NetworksDataState[]> => {
const mnemonicServer = await getInternetCredentials("mnemonicServer");
const mnemonic = mnemonicServer;
if (!mnemonic) {
throw new Error("Mnemonic not found");
}
const hdNode = HDNode.fromMnemonic(mnemonic);
const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`;
const node = hdNode.derivePath(hdPath);
let address;
switch (newNetworkData.namespace) {
case EIP155:
address = node.address;
break;
case COSMOS:
address = (
await getCosmosAccountByHDPath(
mnemonic,
hdPath,
newNetworkData.addressPrefix,
)
).data.address;
break;
default:
throw new Error("Unsupported namespace");
}
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
await Promise.all([
setInternetCredentials(
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
"_",
accountInfo,
),
setInternetCredentials(
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
"_",
"1",
),
setInternetCredentials(
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
"_",
"0",
),
]);
const retrievedNetworksData = await storeNetworkData(newNetworkData);
// Get number of accounts in first network
const nextAccountId = await getNextAccountId(
`${retrievedNetworksData[0].namespace}:${retrievedNetworksData[0].chainId}`,
);
const selectedNetwork = retrievedNetworksData.find(
(network) => network.chainId === newNetworkData.chainId,
);
await addAccountsForNetwork(selectedNetwork!, nextAccountId - 1);
return retrievedNetworksData;
}
const storeNetworkData = async ( const storeNetworkData = async (
networkData: NetworksFormData, networkData: NetworksFormData,
): Promise<NetworksDataState[]> => { ): Promise<NetworksDataState[]> => {
@ -251,13 +180,11 @@ const storeNetworkData = async (
networkId: String(networkId), networkId: String(networkId),
}, },
]; ];
await setInternetCredentials( await setInternetCredentials(
'networks', 'networks',
'_', '_',
JSON.stringify(updatedNetworks), JSON.stringify(updatedNetworks),
); );
return updatedNetworks; return updatedNetworks;
}; };
@ -267,9 +194,7 @@ const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
if(!networks){ if(!networks){
return []; return [];
} }
const parsedNetworks: NetworksDataState[] = JSON.parse(networks); const parsedNetworks: NetworksDataState[] = JSON.parse(networks);
return parsedNetworks; return parsedNetworks;
}; };
@ -292,7 +217,6 @@ export const retrieveAccountsForNetwork = async (
address, address,
hdPath: path, hdPath: path,
}; };
return account; return account;
}), }),
); );
@ -310,7 +234,6 @@ const retrieveAccounts = async (
if (!accountIndices) { if (!accountIndices) {
return; return;
} }
const loadedAccounts = await retrieveAccountsForNetwork( const loadedAccounts = await retrieveAccountsForNetwork(
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`, `${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
accountIndices, accountIndices,
@ -445,28 +368,6 @@ const getCosmosAccountByHDPath = async (
return { cosmosWallet, data }; return { cosmosWallet, data };
}; };
const checkNetworkForChainID = async (
chainId: string,
): Promise<boolean> => {
const networks = await getInternetCredentials('networks');
if (!networks) {
return false;
}
const networksData: NetworksFormData[] = JSON.parse(networks);
return networksData.some((network) => network.chainId === chainId);
}
const isWalletCreated = async (
): Promise<boolean> => {
const mnemonicServer = await getInternetCredentials("mnemonicServer");
const mnemonic = mnemonicServer;
return mnemonic !== null;
};
export { export {
createWallet, createWallet,
addAccount, addAccount,
@ -481,7 +382,4 @@ export {
getNextAccountId, getNextAccountId,
updateAccountCounter, updateAccountCounter,
getCosmosAccountByHDPath, getCosmosAccountByHDPath,
addNewNetwork,
checkNetworkForChainID,
isWalletCreated
}; };

View File

@ -18,6 +18,30 @@ export const DEFAULT_NETWORKS: NetworksFormData[] = [
gasPrice: '0.001', gasPrice: '0.001',
isDefault: true, isDefault: true,
}, },
{
chainId: 'zenith-testnet-stage0',
networkName: 'zenithd stage 0 testnet',
namespace: COSMOS,
rpcUrl: import.meta.env.REACT_APP_ZENITHD_RPC_URL || '',
blockExplorerUrl: '',
nativeDenom: 'znt',
addressPrefix: 'zenith',
coinType: '118',
gasPrice: '0.01',
isDefault: true,
},
{
chainId: 'zenith-testnet-stage1',
networkName: 'zenithd stage 1 testnet',
namespace: COSMOS,
rpcUrl: import.meta.env.REACT_APP_ZENITHD_RPC_URL || '',
blockExplorerUrl: '',
nativeDenom: 'znt',
addressPrefix: 'zenith',
coinType: '118',
gasPrice: '0.01',
isDefault: true,
},
{ {
chainId: 'laconic_9000-1', chainId: 'laconic_9000-1',
networkName: 'laconicd', networkName: 'laconicd',
@ -74,7 +98,6 @@ export const REQUEST_ACCOUNT_PK = 'REQUEST_ACCOUNT_PK';
export const REQUEST_ADD_ACCOUNT = 'REQUEST_ADD_ACCOUNT'; export const REQUEST_ADD_ACCOUNT = 'REQUEST_ADD_ACCOUNT';
export const AUTO_SIGN_IN = 'AUTO_SIGN_IN'; export const AUTO_SIGN_IN = 'AUTO_SIGN_IN';
export const CHECK_BALANCE = 'CHECK_BALANCE'; export const CHECK_BALANCE = 'CHECK_BALANCE';
export const REQUEST_ADD_NETWORK = 'REQUEST_ADD_NETWORK';
// iframe response types // iframe response types
export const COSMOS_ACCOUNTS_RESPONSE = 'COSMOS_ACCOUNTS_RESPONSE'; export const COSMOS_ACCOUNTS_RESPONSE = 'COSMOS_ACCOUNTS_RESPONSE';
@ -86,6 +109,3 @@ export const ACCOUNT_PK_RESPONSE = 'ACCOUNT_PK_RESPONSE';
export const ADD_ACCOUNT_RESPONSE = 'ADD_ACCOUNT_RESPONSE'; export const ADD_ACCOUNT_RESPONSE = 'ADD_ACCOUNT_RESPONSE';
export const WALLET_ACCOUNTS_DATA = 'WALLET_ACCOUNTS_DATA'; export const WALLET_ACCOUNTS_DATA = 'WALLET_ACCOUNTS_DATA';
export const IS_SUFFICIENT = 'IS_SUFFICIENT'; export const IS_SUFFICIENT = 'IS_SUFFICIENT';
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

@ -10,6 +10,7 @@ echo "WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}"
echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}" echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}"
echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}" echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}"
echo "CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL}" echo "CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL}"
echo "CERC_ZENITHD_RPC_URL: ${CERC_ZENITHD_RPC_URL}"
echo "CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}" echo "CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}"
# Build with required env # Build with required env
@ -17,6 +18,7 @@ export REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_ID
export REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE export REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE
export REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT export REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT
export REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL export REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL
export REACT_APP_ZENITHD_RPC_URL=$CERC_ZENITHD_RPC_URL
export REACT_APP_ALLOWED_URLS=$CERC_ALLOWED_URLS export REACT_APP_ALLOWED_URLS=$CERC_ALLOWED_URLS
# Set env variables in build # Set env variables in build