Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7ad34cc01 | ||
|
|
a37dc0973c | ||
|
|
cbb28a6eb3 |
+3
-1
@@ -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=
|
||||
|
||||
+58
@@ -4,6 +4,8 @@ import { TxBody, AuthInfo } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||
|
||||
import { SignClientTypes } from "@walletconnect/types";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing";
|
||||
import { SigningStargateClient } from "@cosmjs/stargate";
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackNavigationProp,
|
||||
@@ -36,6 +38,8 @@ import styles from "./styles/stylesheet";
|
||||
import { Header } from "./components/Header";
|
||||
import { WalletEmbed } from "./screens/WalletEmbed";
|
||||
import { AutoSignIn } from "./screens/AutoSignIn";
|
||||
import { checkSufficientFunds, getPathKey, sendMessage } from "./utils/misc";
|
||||
import useAccountsData from "./hooks/useAccountsData";
|
||||
|
||||
const Stack = createStackNavigator<StackParamsList>();
|
||||
|
||||
@@ -45,6 +49,8 @@ const App = (): React.JSX.Element => {
|
||||
const { web3wallet, setActiveSessions } = useWalletConnect();
|
||||
const { accounts, setCurrentIndex } = useAccounts();
|
||||
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
||||
const { getAccountsData } = useAccountsData();
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [toastVisible, setToastVisible] = useState(false);
|
||||
const [currentProposal, setCurrentProposal] = useState<
|
||||
@@ -219,6 +225,58 @@ const App = (): React.JSX.Element => {
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleCheckBalance = async (event: MessageEvent) => {
|
||||
if (event.data.type !== 'CHECK_BALANCE') return;
|
||||
|
||||
const { chainId, amount } = event.data;
|
||||
const network = networksData.find(net => net.chainId === chainId);
|
||||
|
||||
if (!network) {
|
||||
console.error('Network not found');
|
||||
throw new Error('Requested network not supported.');
|
||||
}
|
||||
|
||||
if (network.namespace !== COSMOS) {
|
||||
throw new Error('Unsupported network');
|
||||
}
|
||||
|
||||
const accounts = await getAccountsData(chainId);
|
||||
const account = accounts[0];
|
||||
|
||||
if (!account) {
|
||||
throw new Error(`No accounts in network ${chainId}`);
|
||||
}
|
||||
|
||||
|
||||
const cosmosPrivKey = (
|
||||
await getPathKey(`${network.namespace}:${chainId}`, account.index)
|
||||
).privKey;
|
||||
|
||||
const sender = await DirectSecp256k1Wallet.fromKey(
|
||||
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
|
||||
network.addressPrefix
|
||||
);
|
||||
|
||||
const client = await SigningStargateClient.connectWithSigner(network.rpcUrl!, sender);
|
||||
|
||||
const balance = await client.getBalance(
|
||||
account.address,
|
||||
network.nativeDenom!.toLowerCase()
|
||||
);
|
||||
|
||||
const areFundsSufficient = checkSufficientFunds(amount, balance.amount);
|
||||
|
||||
sendMessage(event.source as Window, 'IS_SUFFICIENT', areFundsSufficient, event.origin);
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleCheckBalance);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', handleCheckBalance);
|
||||
};
|
||||
}, [networksData, getAccountsData]);
|
||||
|
||||
const showWalletConnect = useMemo(() => accounts.length > 0, [accounts]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { NetworksDataState } from '../types';
|
||||
import { retrieveNetworksData, storeNetworkData } from '../utils/accounts';
|
||||
import { retrieveNetworksData } from '../utils/accounts';
|
||||
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
|
||||
import { setInternetCredentials } from '../utils/key-store';
|
||||
|
||||
const NetworksContext = createContext<{
|
||||
networksData: NetworksDataState[];
|
||||
@@ -27,22 +28,34 @@ const useNetworks = () => {
|
||||
return networksContext;
|
||||
};
|
||||
|
||||
const DEFAULT_NETWORKS_DATA = DEFAULT_NETWORKS.map((defaultNetwork, index) => (
|
||||
{
|
||||
...defaultNetwork,
|
||||
networkId: index.toString()
|
||||
})
|
||||
);
|
||||
|
||||
const NetworksProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [networksData, setNetworksData] = useState<NetworksDataState[]>([]);
|
||||
const [networksData, setNetworksData] = useState<NetworksDataState[]>(DEFAULT_NETWORKS_DATA);
|
||||
const [networkType, setNetworkType] = useState<string>(EIP155);
|
||||
const [selectedNetwork, setSelectedNetwork] = useState<NetworksDataState>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const retrievedNetworks = await retrieveNetworksData();
|
||||
let retrievedNetworks = await retrieveNetworksData();
|
||||
|
||||
if (retrievedNetworks.length === 0) {
|
||||
for (const defaultNetwork of DEFAULT_NETWORKS) {
|
||||
await storeNetworkData(defaultNetwork);
|
||||
}
|
||||
setInternetCredentials(
|
||||
'networks',
|
||||
'_',
|
||||
JSON.stringify(DEFAULT_NETWORKS_DATA),
|
||||
);
|
||||
|
||||
retrievedNetworks = DEFAULT_NETWORKS_DATA;
|
||||
}
|
||||
const retrievedNewNetworks = await retrieveNetworksData();
|
||||
setNetworksData(retrievedNewNetworks);
|
||||
setSelectedNetwork(retrievedNewNetworks[0]);
|
||||
|
||||
setNetworksData(retrievedNetworks);
|
||||
setSelectedNetwork(retrievedNetworks[0]);
|
||||
};
|
||||
|
||||
if (networksData.length === 0) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { retrieveAccounts } from "../utils/accounts";
|
||||
import { useNetworks } from "../context/NetworksContext";
|
||||
|
||||
const useAccountsData = () => {
|
||||
const { networksData } = useNetworks();
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { createWallet } from "../utils/accounts";
|
||||
import { sendMessage } from "../utils/misc";
|
||||
import useAccountsData from "./useAccountsData";
|
||||
import { useNetworks } from "../context/NetworksContext";
|
||||
|
||||
const useGetOrCreateAccounts = () => {
|
||||
const { networksData } = useNetworks();
|
||||
const { getAccountsData } = useAccountsData();
|
||||
|
||||
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;
|
||||
+12
-62
@@ -1,43 +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<Account[]> => {
|
||||
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 sendMessage = (
|
||||
source: Window | null,
|
||||
type: string,
|
||||
data: any,
|
||||
origin: string
|
||||
): void => {
|
||||
source?.postMessage({ type, data }, origin);
|
||||
};
|
||||
const { getAccountsData } = useAccountsData();
|
||||
|
||||
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) {
|
||||
@@ -56,41 +39,8 @@ export const AutoSignIn = () => {
|
||||
};
|
||||
}, [networksData, getAccountsData]);
|
||||
|
||||
useEffect(() => {
|
||||
const getAccountAddress = async (event: MessageEvent) => {
|
||||
if (event.data.type !== 'GET_ACCOUNT_ADDRESS') return;
|
||||
let isNewUser = false;
|
||||
|
||||
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);
|
||||
|
||||
isNewUser = true
|
||||
|
||||
// Re-fetch newly created accounts
|
||||
accountsData = await getAccountsData(event.data.chainId);
|
||||
}
|
||||
|
||||
if (!accountsData.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage(event.source as Window, 'ACCOUNT_ADDRESS_RESPONSE', { address: accountsData[0].address, isNewUser }, event.origin);
|
||||
};
|
||||
|
||||
window.addEventListener('message', getAccountAddress);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', getAccountAddress);
|
||||
};
|
||||
}, [networksData, getAccountsData]);
|
||||
// Custom hook for adding listener to get accounts data
|
||||
useGetOrCreateAccounts();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -15,15 +15,17 @@ import {
|
||||
SigningStargateClient,
|
||||
} from '@cosmjs/stargate';
|
||||
|
||||
import { createWallet, retrieveAccounts, retrieveSingleAccount } from '../utils/accounts';
|
||||
import { retrieveSingleAccount } from '../utils/accounts';
|
||||
import AccountDetails from '../components/AccountDetails';
|
||||
import styles from '../styles/stylesheet';
|
||||
import DataBox from '../components/DataBox';
|
||||
import { getPathKey } from '../utils/misc';
|
||||
import { checkSufficientFunds, getPathKey, sendMessage } from '../utils/misc';
|
||||
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';
|
||||
import useAccountsData from '../hooks/useAccountsData';
|
||||
|
||||
type TransactionDetails = {
|
||||
chainId: string;
|
||||
@@ -45,38 +47,7 @@ export const WalletEmbed = () => {
|
||||
const txEventRef = useRef<MessageEvent | null>(null);
|
||||
|
||||
const { networksData } = useNetworks();
|
||||
|
||||
const getAccountsData = useCallback(async (chainId: string): Promise<string[]> => {
|
||||
const targetNetwork = networksData.find(network => network.chainId === chainId);
|
||||
|
||||
if (!targetNetwork) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const accounts = await retrieveAccounts(targetNetwork);
|
||||
|
||||
if (!accounts || accounts.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return accounts.map(account => account.address);
|
||||
}, [networksData]);
|
||||
|
||||
const sendMessage = (
|
||||
source: Window | null,
|
||||
type: string,
|
||||
data: any,
|
||||
origin: string
|
||||
): void => {
|
||||
source?.postMessage({ type, data }, origin);
|
||||
};
|
||||
|
||||
const checkSufficientFunds = (amount: string, balance: string) => {
|
||||
const amountBigNum = BigNumber.from(String(amount));
|
||||
const balanceBigNum = BigNumber.from(balance);
|
||||
|
||||
return balanceBigNum.gt(amountBigNum);
|
||||
};
|
||||
const { getAccountsData } = useAccountsData();
|
||||
|
||||
useEffect(() => {
|
||||
const handleGetAccounts = async (event: MessageEvent) => {
|
||||
@@ -99,29 +70,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();
|
||||
|
||||
const handleTxRequested = useCallback(
|
||||
async (event: MessageEvent) => {
|
||||
@@ -342,7 +292,7 @@ export const WalletEmbed = () => {
|
||||
</>
|
||||
) : (
|
||||
<View style={styles.spinnerContainer}>
|
||||
<View style={{marginTop: 50}}></View>
|
||||
<View style={{ marginTop: 50 }}></View>
|
||||
<ActivityIndicator size="large" color="#0000ff" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData';
|
||||
import { EIP155_CHAINS } from './wallet-connect/EIP155Data';
|
||||
import { NetworksFormData } from '../types';
|
||||
|
||||
export const EIP155 = 'eip155';
|
||||
export const COSMOS = 'cosmos';
|
||||
export const DEFAULT_NETWORKS = [
|
||||
|
||||
export const DEFAULT_NETWORKS: NetworksFormData[] = [
|
||||
{
|
||||
chainId: 'laconic-testnet-2',
|
||||
networkName: 'laconicd testnet-2',
|
||||
|
||||
+22
-4
@@ -1,7 +1,11 @@
|
||||
/* Importing this library provides react native with a secure random source.
|
||||
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
||||
import 'react-native-get-random-values';
|
||||
import { BigNumber } from 'ethers';
|
||||
|
||||
import { AccountData } from '@cosmjs/amino';
|
||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
||||
import { stringToPath } from '@cosmjs/crypto';
|
||||
import '@ethersproject/shims';
|
||||
|
||||
import {
|
||||
@@ -9,10 +13,6 @@ import {
|
||||
resetInternetCredentials,
|
||||
setInternetCredentials,
|
||||
} from './key-store';
|
||||
|
||||
import { AccountData } from '@cosmjs/amino';
|
||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
||||
import { stringToPath } from '@cosmjs/crypto';
|
||||
import { EIP155 } from './constants';
|
||||
import { NetworksDataState } from '../types';
|
||||
|
||||
@@ -149,10 +149,28 @@ const resetKeyServers = async (namespace: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const sendMessage = (
|
||||
source: Window | null,
|
||||
type: string,
|
||||
data: any,
|
||||
origin: string
|
||||
): void => {
|
||||
source?.postMessage({ type, data }, origin);
|
||||
};
|
||||
|
||||
const checkSufficientFunds = (amount: string, balance: string) => {
|
||||
const amountBigNum = BigNumber.from(String(amount));
|
||||
const balanceBigNum = BigNumber.from(balance);
|
||||
|
||||
return balanceBigNum.gte(amountBigNum);
|
||||
};
|
||||
|
||||
export {
|
||||
getMnemonic,
|
||||
getPathKey,
|
||||
updateAccountIndices,
|
||||
getHDPath,
|
||||
resetKeyServers,
|
||||
sendMessage,
|
||||
checkSufficientFunds,
|
||||
};
|
||||
|
||||
@@ -10,6 +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_DEPLOY_APP_URL: ${CERC_DEPLOY_APP_URL}
|
||||
command: ["bash", "/scripts/run.sh"]
|
||||
volumes:
|
||||
- ../config/app/run.sh:/scripts/run.sh
|
||||
|
||||
@@ -10,12 +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_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_DEPLOY_APP_URL=$CERC_DEPLOY_APP_URL \
|
||||
yarn build
|
||||
|
||||
# Define the directory and file path
|
||||
|
||||
@@ -63,6 +63,10 @@ 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=
|
||||
|
||||
# Deploy app URL used for checking origin of the messages for auto-sign-in route
|
||||
# Deploy app repo: https://git.vdb.to/cerc-io/snowballtools-base
|
||||
CERC_DEPLOY_APP_URL=
|
||||
```
|
||||
|
||||
## Start the deployment
|
||||
|
||||
Reference in New Issue
Block a user