Add support for wallet_getCapabilities from WalletConnect (#39)

Part of https://www.notion.so/Integrate-eSIM-buy-flow-into-app-18aa6b22d47280d4a77cf1b27e2ba193

- Add Base network
- Check sufficient balance for Eth tx fees

Co-authored-by: pranavjadhav007 <jadhavpranav89@gmail.com>
Reviewed-on: LaconicNetwork/laconic-wallet-web#39
This commit is contained in:
2025-07-15 16:05:02 +00:00
co-authored by pranav
parent 1e88321490
commit 96c0a4df06
10 changed files with 109 additions and 61 deletions
+18
View File
@@ -147,6 +147,23 @@ const App = (): React.JSX.Element => {
}); });
break; break;
case EIP155_SIGNING_METHODS.WALLET_GET_CAPABILITIES:
const supportedNetworks = networksData
.filter(network => network.namespace === EIP155)
.map(network => `${network.namespace}:${network.chainId}`);
const capabilitiesResponse = formatJsonRpcResult(id, {
accountManagement: true,
sessionManagement: true,
supportedAuthMethods: ['personal_sign', 'eth_sendTransaction'],
supportedNetworks: supportedNetworks,
});
await web3wallet!.respondSessionRequest({
topic,
response: capabilitiesResponse,
});
break;
case COSMOS_METHODS.COSMOS_SIGN_DIRECT: case COSMOS_METHODS.COSMOS_SIGN_DIRECT:
const message = { const message = {
txbody: TxBody.toJSON( txbody: TxBody.toJSON(
@@ -347,6 +364,7 @@ const App = (): React.JSX.Element => {
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
headerRight: () => ( headerRight: () => (
<Button <Button
testID="pair-button"
onPress={() => { onPress={() => {
navigation.navigate("AddSession"); navigation.navigate("AddSession");
}} }}
+1 -1
View File
@@ -104,7 +104,7 @@ export const Header: React.FC<{
</Stack> </Stack>
{showWalletConnect && ( {showWalletConnect && (
<Button onClick={() => navigation.navigate("WalletConnect")}> <Button data-webviewId="wallet-connect-button" onClick={() => navigation.navigate("WalletConnect")}>
{<WCLogo />} {<WCLogo />}
</Button> </Button>
)} )}
+17 -13
View File
@@ -283,19 +283,23 @@ const PairingModal = ({
</View> </View>
</ScrollView> </ScrollView>
<View style={styles.flexRow}> {currentProposal && namespaces && (
<Button <View style={styles.flexRow}>
mode="contained" <Button
onPress={handleAccept} mode="contained"
loading={isLoading} testID="accept-pair-request-button"
disabled={isLoading}> onPress={handleAccept}
{isLoading ? 'Connecting' : 'Yes'} loading={isLoading}
</Button> disabled={isLoading}>
<View style={styles.space} /> {isLoading ? 'Connecting' : 'Yes'}
<Button mode="outlined" onPress={handleReject}> </Button>
No <View style={styles.space} />
</Button> <Button mode="outlined" onPress={handleReject}>
</View> No
</Button>
</View>
)}
</View> </View>
</View> </View>
</Modal> </Modal>
+1 -1
View File
@@ -44,7 +44,7 @@ const AddSession = () => {
/> />
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
<Button variant="contained" onClick={pair}> <Button variant="contained" data-webviewId="pair-session-button" onClick={pair}>
Pair Session Pair Session
</Button> </Button>
</Box> </Box>
+35 -25
View File
@@ -82,23 +82,27 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
useState<BigNumber | null>(); useState<BigNumber | null>();
const isSufficientFunds = useMemo(() => { const isSufficientFunds = useMemo(() => {
if (!transaction.value) {
return;
}
if (!balance) { if (!balance) {
return; return;
} }
const amountBigNum = BigNumber.from(String(transaction.value)); if (!fees) {
const balanceBigNum = BigNumber.from(balance); return;
if (amountBigNum.gte(balanceBigNum)) {
return false;
} else {
return true;
} }
}, [balance, transaction]);
const balanceBigNum = BigNumber.from(balance);
const feesBigNum = BigNumber.from(fees);
let totalRequiredBigNum = feesBigNum;
if (transaction.value) {
const amountBigNum = BigNumber.from(String(transaction.value));
totalRequiredBigNum = amountBigNum.add(feesBigNum);
}
// Compare the user's balance with the total required amount
const isSufficient = balanceBigNum.gte(totalRequiredBigNum);
return isSufficient;
}, [balance, transaction.value, fees]);
const requestedNetwork = networksData.find( const requestedNetwork = networksData.find(
networkData => networkData =>
@@ -273,8 +277,12 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
useEffect(() => { useEffect(() => {
if (namespace === EIP155) { if (namespace === EIP155) {
const ethFees = BigNumber.from(ethGasLimit ?? 0) if (!ethGasLimit || !(ethMaxFee || ethGasPrice)){
.mul(BigNumber.from(ethMaxFee ?? ethGasPrice ?? 0)) return;
}
const ethFees = BigNumber.from(ethGasLimit)
.mul(BigNumber.from(ethMaxFee ?? ethGasPrice))
.toString(); .toString();
setFees(ethFees); setFees(ethFees);
} else { } else {
@@ -495,7 +503,7 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
useEffect(() => { useEffect(() => {
const getEthGas = async () => { const getEthGas = async () => {
try { try {
if (!isSufficientFunds || !provider) { if (!provider) {
return; return;
} }
@@ -568,11 +576,11 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
}, [cosmosStargateClient, isSufficientFunds, sendMsg, transaction,txMemo]); }, [cosmosStargateClient, isSufficientFunds, sendMsg, transaction,txMemo]);
useEffect(() => { useEffect(() => {
if (balance && !isSufficientFunds) { if (balance && !isSufficientFunds && !fees) {
setTxError('Insufficient funds'); setTxError('Insufficient funds');
setIsTxErrorDialogOpen(true); setIsTxErrorDialogOpen(true);
} }
}, [isSufficientFunds, balance]); }, [isSufficientFunds, balance, fees]);
return ( return (
<> <>
@@ -614,14 +622,16 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
{transaction && ( {transaction && (
<View style={styles.approveTransfer}> <View style={styles.approveTransfer}>
<DataBox label="To" data={transaction.to!} /> <DataBox label="To" data={transaction.to!} />
<DataBox {transaction.value !== undefined && transaction.value !== null && (
label={`Amount (${ <DataBox
namespace === EIP155 ? 'wei' : requestedNetwork!.nativeDenom label={`Amount (${
})`} namespace === EIP155 ? 'wei' : requestedNetwork!.nativeDenom
data={BigNumber.from( })`}
transaction.value?.toString(), data={BigNumber.from(
).toString()} transaction.value?.toString(),
/> ).toString()}
/>
)}
{namespace === COSMOS && ( {namespace === COSMOS && (
<DataBox <DataBox
label="Memo" label="Memo"
+3 -2
View File
@@ -116,7 +116,8 @@ const SignRequest = ({ route }: SignRequestProps) => {
); );
useEffect(() => { useEffect(() => {
if (route.path) { const requestEvent = route.params.requestEvent;
if (route.path && !requestEvent) {
const sanitizedRoute = sanitizePath(route.path); const sanitizedRoute = sanitizePath(route.path);
sanitizedRoute && sanitizedRoute &&
retrieveData( retrieveData(
@@ -127,7 +128,6 @@ const SignRequest = ({ route }: SignRequestProps) => {
); );
return; return;
} }
const requestEvent = route.params.requestEvent;
const requestChainId = requestEvent?.params.chainId; const requestChainId = requestEvent?.params.chainId;
const requestedChain = networksData.find( const requestedChain = networksData.find(
@@ -310,6 +310,7 @@ const SignRequest = ({ route }: SignRequestProps) => {
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<Button <Button
mode="contained" mode="contained"
testID="accept-sign-request-button"
onPress={signMessageHandler} onPress={signMessageHandler}
loading={isApproving} loading={isApproving}
disabled={isApproving}> disabled={isApproving}>
+13 -8
View File
@@ -43,15 +43,20 @@ export default function WalletConnect() {
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
left={() => ( left={() => (
<> <>
{session.peer.metadata.icons[0].endsWith(".svg") ? ( {session.peer.metadata.icons && session.peer.metadata.icons.length > 0 ? (
<View style={styles.dappLogo}> session.peer.metadata.icons[0].endsWith(".svg") ? (
<Text>SvgURI peerMetaDataIcon</Text> <View style={styles.dappLogo}>
</View> <Text>SvgURI peerMetaDataIcon</Text>
</View>
) : (
<Image
style={styles.dappLogo}
source={{ uri: session.peer.metadata.icons[0] }}
/>
)
) : ( ) : (
<Image // Render nothing if no icon is available
style={styles.dappLogo} <View style={styles.dappLogo} /> // Or simply null
source={{ uri: session.peer.metadata.icons[0] }}
/>
)} )}
</> </>
)} )}
+1 -1
View File
@@ -344,7 +344,7 @@ const retrieveSingleAccount = async (
throw new Error('Accounts for given chain not found'); throw new Error('Accounts for given chain not found');
} }
return loadedAccounts.find(account => account.address === address); return loadedAccounts.find(account => account.address.toLowerCase() === address.toLowerCase());
}; };
const resetWallet = async () => { const resetWallet = async () => {
+12
View File
@@ -40,6 +40,18 @@ export const DEFAULT_NETWORKS: NetworksFormData[] = [
coinType: '60', coinType: '60',
isDefault: true, isDefault: true,
}, },
// Base Chain Network
{
chainId: '8453',
networkName: EIP155_CHAINS['eip155:8453'].name,
namespace: EIP155,
rpcUrl: EIP155_CHAINS['eip155:8453'].rpc,
blockExplorerUrl: '',
currencySymbol: 'ETH',
coinType: '60',
isDefault: true,
},
{ {
chainId: 'provider', chainId: 'provider',
networkName: COSMOS_TESTNET_CHAINS['cosmos:provider'].name, networkName: COSMOS_TESTNET_CHAINS['cosmos:provider'].name,
+7 -9
View File
@@ -11,13 +11,8 @@
export type TEIP155Chain = keyof typeof EIP155_CHAINS; export type TEIP155Chain = keyof typeof EIP155_CHAINS;
export type EIP155Chain = { export type EIP155Chain = {
chainId: number;
name: string; name: string;
logo: string;
rgb: string;
rpc: string; rpc: string;
namespace: string;
smartAccountEnabled?: boolean;
}; };
/** /**
@@ -25,12 +20,14 @@ export type EIP155Chain = {
*/ */
export const EIP155_CHAINS: Record<string, EIP155Chain> = { export const EIP155_CHAINS: Record<string, EIP155Chain> = {
'eip155:1': { 'eip155:1': {
chainId: 1,
name: 'Ethereum', name: 'Ethereum',
logo: '/chain-logos/eip155-1.png',
rgb: '99, 125, 234',
rpc: 'https://cloudflare-eth.com/', rpc: 'https://cloudflare-eth.com/',
namespace: 'eip155', },
// Ref: https://docs.base.org/base-chain/quickstart/connecting-to-base#base-mainnet
'eip155:8453': {
name: 'Base',
rpc: 'https://mainnet.base.org',
}, },
}; };
@@ -40,4 +37,5 @@ export const EIP155_CHAINS: Record<string, EIP155Chain> = {
export const EIP155_SIGNING_METHODS = { export const EIP155_SIGNING_METHODS = {
PERSONAL_SIGN: 'personal_sign', PERSONAL_SIGN: 'personal_sign',
ETH_SEND_TRANSACTION: 'eth_sendTransaction', ETH_SEND_TRANSACTION: 'eth_sendTransaction',
WALLET_GET_CAPABILITIES: 'wallet_getCapabilities'
}; };