-
Notifications
You must be signed in to change notification settings - Fork 38
Description
Description
Description
I am experiencing an issue where MetaMask opens but fails to show the transaction confirmation (approve) popup.
Even though my domain is fully verified and SIWX is configured, the transaction request seems to be ignored by MetaMask after the redirect.
Environment
- SDK:
@reown/appkit-ethers-react-native - Platform: Android (Physical Device)
- Wallet: MetaMask (Latest version)
- Domain Status: Confirmed as
paykingos.com: verifiedviaadb shell pm get-app-links
Steps to Reproduce
- Configure
AppKitwithReownAuthenticationandlinkMode: false(to use standard deep links). - Connect wallet and complete SIWX (Sign-In With Ethereum) successfully.
- Call
contract.approve()usingethersBrowserProvider. - The app redirects to MetaMask, but no transaction popup appears.
What I've Already Tried
- Set
linkMode: falseto avoid Universal Link parsing issues in MetaMask. - Added
staticNetwork: truetoBrowserProviderand explicitly injectedchainId: 137. - Removed all custom gas options to let MetaMask handle estimation.
- Verified that the
AppKitconfiguration is initialized outside the component tree (api/appkit.ts). - Cleared MetaMask activity and cache.
Code Snippets
AppKit Config:
export const appKit = createAppKit({
projectId: 'YOUR_PROJECT_ID',
metadata: {
name: 'PayKing OS',
url: '[https://paykingos.com](https://paykingos.com)',
redirect: {
native: 'paykingos://',
universal: '[https://paykingos.com](https://paykingos.com)',
linkMode: false,
},
},
siwx: new ReownAuthentication(),
networks: [POLYGON_MAINNET],
defaultNetwork: POLYGON_MAINNET,
});Additional Context
The SIWX "Sign In" prompt works perfectly and returns to the app. The issue only happens during the eth_sendTransaction (approve) call. It seems like the deep link payload is not being correctly interpreted by MetaMask on Android.
AppKit SDK version
"@reown/appkit-common": "^1.8.15", "@reown/appkit-ethers-react-native": "^2.0.1", "@reown/appkit-react-native": "^2.0.1",
Output of npx react-native info
System:
OS: macOS 15.5
CPU: (8) arm64 Apple M1 Pro
Memory: 204.23 MB / 16.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 23.10.0
path: /Users/youngju/.nvm/versions/node/v23.10.0/bin/node
Yarn:
version: 1.22.19
path: /opt/homebrew/bin/yarn
npm:
version: 10.9.2
path: /Users/youngju/.nvm/versions/node/v23.10.0/bin/npm
Watchman: Not Found
Managers:
CocoaPods: Not Found
SDKs:
iOS SDK: Not Found
Android SDK: Not Found
IDEs:
Android Studio: 2025.2 AI-252.27397.103.2522.14514259
Xcode:
version: /undefined
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.17
path: /opt/homebrew/Cellar/openjdk@17/17.0.17/libexec/openjdk.jdk/Contents/Home/bin/javac
Ruby:
version: 3.4.2
path: /opt/homebrew/opt/ruby/bin/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.0.0
wanted: 20.0.0
react:
installed: 19.2.0
wanted: 19.2.0
react-native:
installed: 0.83.1
wanted: 0.83.1
react-native-macos: Not Found
npmGlobalPackages:
"react-native": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: Not found
newArchEnabled: false
Expo Version (if applies)
No response
Steps to reproduce
Environment Setup: Ensure the Android app is configured with @reown/appkit-ethers-react-native and the domain paykingos.com is verified with verified status via adb shell pm get-app-links.
Initialization: Initialize AppKit outside the component tree with ReownAuthentication for SIWX and linkMode: false to use standard deep links.
Wallet Connection: Launch the app, open the AppKit modal, and select MetaMask.
Authentication: Complete the SIWX (Sign-In With Ethereum) process. The app should redirect to MetaMask for signing and return to the dapp successfully.
Network Check: Ensure the wallet is on Polygon Mainnet (Chain ID: 137).
Trigger Transaction: Call the approve function of an ERC20 contract using ethers.js BrowserProvider.
The Bug: The app redirects to the MetaMask app, but no transaction confirmation popup appears. MetaMask just stays on the wallet home screen.
Snack, code example, screenshot, or link to a repository
import { useState } from 'react';
import { Alert } from 'react-native';
import { BrowserProvider, Contract, parseUnits } from 'ethers';
import { useAccount, useProvider } from '@reown/appkit-react-native';
import { ERC20_ABI } from '../constants/abis';
import { CONFIG, TOKENS, GAS_OPTIONS } from '../constants/index';
export const useApprove = () => {
const [loading, setLoading] = useState(false);
const { provider } = useProvider(); // AppKit에서 제공하는 지갑 공급자
const { isConnected, address } = useAccount();
const requestApprove = async (
tokenSymbol: 'USDT' | 'USDC',
amount: string,
) => {
if (!isConnected || !provider) return;
setLoading(true);
try {
// 🚀 [추가] 지갑에 폴리곤(0x89)으로 네트워크 전환 명령을 내립니다.
// 메타마스크가 이더리움에 있어도 이 코드를 만나면 폴리곤 전환 창을 띄웁니다.
await (provider as any).request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x89' }], // 137의 16진수
});
const ethersProvider = new BrowserProvider(
provider,
{
chainId: 137,
name: 'polygon',
},
{ staticNetwork: true },
); // 👈 [핵심] 정적 네트워크 옵션으로 오버헤드 제거
const signer = await ethersProvider.getSigner();
const tokenAddress = TOKENS[tokenSymbol];
const contract = new Contract(tokenAddress, ERC20_ABI, signer);
const parsedAmount = parseUnits(amount, 6);
// [방어 4] 가스 추정 에러 및 정체 방지
// gasLimit을 수동으로 주어 'estimateGas' 에러를 우회하고,
// 사장님의 GAS_OPTIONS(60/120 gwei 등)를 적용해 광속 처리를 유도합니다.
const tx = await contract.approve(CONFIG.SPENDER_ADDR, parsedAmount, {
...GAS_OPTIONS,
gasLimit: 150000,
chainId: 137, // 👈 [추가] 메타마스크가 딴맘 못 먹게 쐐기를 박습니다.
});
console.log(`[${tokenSymbol}] 승인 트랜잭션 제출됨:`, tx.hash);
// [방어 5] 블록 확정 대기
// 사용자가 앱으로 돌아왔을 때 실제 결과가 나올 때까지 기다립니다.
const receipt = await tx.wait(1);
if (receipt.status === 1) {
return { success: true, hash: tx.hash };
} else {
throw new Error('트랜잭션이 블록체인에서 거절되었습니다.');
}
} catch (error: any) {
console.error('Approve 에러:', error);
// 사용자 취소 시 에러 메시지 순화
const errorMessage =
error.code === 'ACTION_REJECTED'
? '사용자가 지갑에서 승인을 거절했습니다.'
: error.reason || error.message || '네트워크 오류가 발생했습니다.';
Alert.alert('승인 실패', errorMessage);
} finally {
setLoading(false);
}
};
return { requestApprove, loading };
};