Skip to content

Commit

Permalink
feat: Support telegram mini app (#199)
Browse files Browse the repository at this point in the history
* feat: Add `ConnectWithWalletConnect` view

* feat: support TMA

* feat: Support telegram mini app
  • Loading branch information
wenty22 authored Aug 27, 2024
1 parent c1c28ec commit fcb528a
Show file tree
Hide file tree
Showing 35 changed files with 482 additions and 166 deletions.
5 changes: 5 additions & 0 deletions .changeset/rude-hornets-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@node-real/walletkit': patch
---

Support telegram mini app
1 change: 1 addition & 0 deletions examples/nextjs/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const config: WalletKitConfig = {
evmConfig({
autoConnect: true,
initialChainId: 1,
walletConnectProjectId: 'e68a1816d39726c2afabf05661a32767',
wallets: [metaMask(), trustWallet(), walletConnect()],
chains: [mainnet] as any[],
}),
Expand Down
1 change: 1 addition & 0 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const config: WalletKitConfig = {
evmConfig({
autoConnect: true,
initialChainId: 1,
walletConnectProjectId: 'e68a1816d39726c2afabf05661a32767',
wallets: [metaMask(), trustWallet(), walletConnect()],
chains: [mainnet] as any[],
}),
Expand Down
2 changes: 1 addition & 1 deletion packages/walletkit/__dev__/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
binanceWeb3Wallet,
bitgetWallet,
coinbaseWallet,
EthereumScript,
evmConfig,
mathWallet,
metaMask,
Expand Down Expand Up @@ -50,6 +49,7 @@ const config: WalletKitConfig = {
evmConfig({
autoConnect: true,
initialChainId: 1,
walletConnectProjectId: 'e68a1816d39726c2afabf05661a32767',
chains: [mainnet, bsc],
wallets: [
metaMask(),
Expand Down
3 changes: 2 additions & 1 deletion packages/walletkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
"build": "vite build"
},
"peerDependencies": {
"@tanstack/react-query": "^5",
"react": ">=17",
"react-dom": ">=17",
"@tanstack/react-query": "^5",
"viem": "^2",
"wagmi": "^2"
},
Expand Down Expand Up @@ -72,6 +72,7 @@
"viem": "^2.17.4",
"vite": "^4.5.3",
"vite-plugin-dts": "^3.9.1",
"vite-plugin-mkcert": "^1.17.6",
"wagmi": "^2.10.10"
}
}
14 changes: 14 additions & 0 deletions packages/walletkit/src/core/base/utils/mobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,17 @@ export function isIOS(): boolean {
export function isMobile(): boolean {
return isAndroid() || isIOS();
}

// telegram mini app
export function isTMA(): boolean {
const check = (host: any) => {
return (
typeof host !== 'undefined' &&
'TelegramWebviewProxy' in host &&
'postEvent' in host.TelegramWebviewProxy &&
typeof host.TelegramWebviewProxy.postEvent === 'function'
);
};

return check(window);
}
13 changes: 5 additions & 8 deletions packages/walletkit/src/core/configs/getDefaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { EvmConfig } from '@/evm/utils/evmConfig';
type DefaultConfig = Pick<WalletKitContextProps, 'appearance' | 'eventConfig' | 'walletConfig'>;

export function getDefaultConfig(config: WalletKitConfig): DefaultConfig {
const evmConfig = config.walletConfigs.find((item) => item.walletType === 'evm') as EvmConfig;
const { appearance, eventConfig, walletConfigs } = config;

const solanaConfig = config.walletConfigs.find(
(item) => item.walletType === 'solana',
) as SolanaConfig;
const evmConfig = walletConfigs.find((item) => item.walletType === 'evm') as EvmConfig;
const solanaConfig = walletConfigs.find((item) => item.walletType === 'solana') as SolanaConfig;

return {
appearance: {
Expand All @@ -27,7 +26,7 @@ export function getDefaultConfig(config: WalletKitConfig): DefaultConfig {

walletDownloadUrl: `https://trustwallet.com/`,

...config.appearance,
...appearance,
},

eventConfig: {
Expand All @@ -43,7 +42,7 @@ export function getDefaultConfig(config: WalletKitConfig): DefaultConfig {
});
}
},
...config.eventConfig,
...eventConfig,
},

walletConfig: {
Expand All @@ -52,5 +51,3 @@ export function getDefaultConfig(config: WalletKitConfig): DefaultConfig {
},
};
}

export const WALLET_CONNECT_PROJECT_ID = 'e68a1816d39726c2afabf05661a32767';
1 change: 1 addition & 0 deletions packages/walletkit/src/core/configs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface BaseWallet extends WalletConfig {
isVisible?: boolean;
render?: (props: WalletRenderProps) => React.ReactNode;
showQRCode?: boolean;
useWalletConnect?: boolean;
isInstalled: () => boolean | undefined;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,27 @@ interface ConnectingViewProps {
runConnect: () => void;
wallet: BaseWallet;
isConnected: boolean;
isReady?: boolean;
}

export function ConnectingView(props: ConnectingViewProps) {
const { status, runConnect, wallet, isConnected } = props;
const { status, runConnect, wallet, isConnected, isReady = true } = props;

const log = useLogger();
const logos = useWalletLogos(wallet.logos);
const downloadUrl = useWalletDownloadUrl(wallet.downloadUrls);

useEffect(() => {
if (status === CONNECT_STATUS.UNAVAILABLE) return;
log('[connecting page]', `name: ${wallet?.name}, status: ${status}`);

if (status === CONNECT_STATUS.UNAVAILABLE || !isReady) return;

const connectTimeout = setTimeout(runConnect, 600);
return () => {
clearTimeout(connectTimeout);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

log('[connecting page]', `name: ${wallet?.name}, status: ${status}`);
}, [isReady]);

const isError = [
CONNECT_STATUS.FAILED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useRef, useMemo, useCallback } from 'react';
import { RouteContext } from './context';
import { EvmConnectingView } from '@/evm/components/EvmConnectingView';
import { EvmConnectWithQRCodeView } from '@/evm/components/EvmConnectWithQRCodeView';
import { EvmConnectWithWalletConnectView } from '@/evm/components/EvmConnectWithWalletConnectView';
import { SolanaConnectingView } from '@/solana/components/SolanaConnectingView';
import { SolanaConnectWithQRCodeView } from '@/solana/components/SolanaConnectWithQRCodeView';
import { ConnectorsView } from '../ConnectorsView';
Expand All @@ -10,6 +11,7 @@ export enum ViewRoutes {
CONNECTORS = 'Connectors',
EVM_CONNECTING = 'EvmConnecting',
EVM_CONNECT_WITH_QRCODE = 'EvmConnectWithQRCode',
EVM_CONNECT_WITH_WALLET_CONNECT = 'EvmConnectWithWalletConnectView',
SOLANA_CONNECTING = 'SolanaConnecting',
SOLANA_CONNECT_WITH_QRCODE = 'SolanaConnectWithQRCode',
}
Expand All @@ -32,6 +34,8 @@ export function RouteProvider(props: RouteProviderProps) {
return <EvmConnectingView />;
case ViewRoutes.EVM_CONNECT_WITH_QRCODE:
return <EvmConnectWithQRCodeView />;
case ViewRoutes.EVM_CONNECT_WITH_WALLET_CONNECT:
return <EvmConnectWithWalletConnectView />;
case ViewRoutes.SOLANA_CONNECTING:
return <SolanaConnectingView />;
case ViewRoutes.SOLANA_CONNECT_WITH_QRCODE:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ThemeProvider } from '../ThemeProvider';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { EvmWalletProvider } from '@/evm/components/EvmWalletProvider';
import { SolanaWalletProvider } from '@/solana/components/SolanaWalletProvider';
import { Action, WalletKitConfig, WalletKitContext } from './context';
Expand All @@ -8,6 +8,7 @@ import { ConnectModalProvider } from '@/core/modals/ConnectModal/provider';
import { ToastProvider } from '@/core/base/components/toast/ToastProvider';
import { BaseWallet } from '@/core/configs/types';
import { ProfileModalProvider } from '@/core/modals/ProfileModal/provider';
import { isTMA } from '@/core/base/utils/mobile';

export interface WalletKitProviderProps {
config: WalletKitConfig;
Expand Down Expand Up @@ -56,6 +57,17 @@ export function WalletKitProvider(props: WalletKitProviderProps) {
};
}, [action, config.debug, finalConfig, selectedWallet, wallets]);

// TMA hack
useEffect(() => {
if (isTMA()) {
window.open = (function (open) {
return function (url, _, features) {
return open.call(window, url, '_blank', features);
};
})(window.open);
}
}, []);

return (
<WalletKitContext.Provider value={value}>
<ToastProvider />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { ConnectWithQRCode } from '@/core/modals/ConnectModal/ConnectWithQRCode';
import { useSelectedWallet } from '@/core/providers/WalletKitProvider/context';
import { useEvmIsConnected } from '@/evm/hooks/useEvmIsConnected';
import { useQRCodeUri } from '@/evm/hooks/useQRCodeUri';
import { useWalletConnectUri } from '@/evm/hooks/useWalletConnectUri';
import { useWalletConnectModal } from '@/evm/hooks/useWalletConnectModal';
import { EvmWallet, isWalletConnect } from '@/evm/wallets';

export function EvmConnectWithQRCodeView() {
const { selectedWallet } = useSelectedWallet();

const wcUri = useQRCodeUri();
const wcUri = useWalletConnectUri();
const wcModal = useWalletConnectModal();
const qrCodeUri = wcUri && ((selectedWallet as EvmWallet).getQRCodeUri?.(wcUri) ?? wcUri);
const qrCodeUri = wcUri && ((selectedWallet as EvmWallet).getUri?.(wcUri) ?? wcUri);
const isConnected = useEvmIsConnected();

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { CONNECT_STATUS } from '@/core/constants';
import { ConnectingView } from '@/core/modals/ConnectModal/ConnectingView';
import { useLogger, useSelectedWallet } from '@/core/providers/WalletKitProvider/context';
import { useEvmIsConnected } from '@/evm/hooks/useEvmIsConnected';
import { useWalletConnectUri } from '@/evm/hooks/useWalletConnectUri';
import { EvmWallet } from '@/evm/wallets';
import { useCallback, useState } from 'react';

export function EvmConnectWithWalletConnectView() {
const { selectedWallet } = useSelectedWallet();

const log = useLogger();
const [status, setStatus] = useState(CONNECT_STATUS.CONNECTING);

const wcUri = useWalletConnectUri({
onError(error: any) {
if (error.code) {
// https://github.com/MetaMask/eth-rpc-errors/blob/main/src/error-constants.ts
switch (error.code) {
case -32002:
setStatus(CONNECT_STATUS.NOTCONNECTED);
break;
case 4001:
setStatus(CONNECT_STATUS.REJECTED);
break;
default:
setStatus(CONNECT_STATUS.FAILED);
break;
}
} else {
// Sometimes the error doesn't respond with a code
if (error.message) {
switch (error.message) {
case 'User rejected request':
setStatus(CONNECT_STATUS.REJECTED);
break;
default:
setStatus(CONNECT_STATUS.FAILED);
break;
}
}
}
},
});

const walletUri = (selectedWallet as EvmWallet).getUri(wcUri);
const isConnected = useEvmIsConnected();

const onClickConnect = useCallback(() => {
log(`[connectWithWalletConnect] walletUri`, walletUri);
setStatus(CONNECT_STATUS.CONNECTING);
window.open(walletUri, '_self', 'noopener noreferrer');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletUri]);

return (
<ConnectingView
isConnected={isConnected}
status={status}
runConnect={onClickConnect}
wallet={selectedWallet}
isReady={!!wcUri}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isMobile, isTMA } from '@/core/base/utils/mobile';
import { UseWalletRenderProps } from '@/core/hooks/useWalletRender';
import { isMobile } from '@/core/index';
import { useConnectModal } from '@/core/modals/ConnectModal/context';
import { ViewRoutes } from '@/core/modals/ConnectModal/RouteProvider';
import { useRouter } from '@/core/modals/ConnectModal/RouteProvider/context';
Expand Down Expand Up @@ -33,7 +33,6 @@ export function SetEvmWalletClickRef(props: SetEvmWalletClickRefProps) {
const router = useRouter();

const timerRef = useRef<any>();
const mobile = isMobile();
const { wallets } = useEvmConfig();

clickRef.current = (walletId: string, e: React.MouseEvent<Element, MouseEvent>) => {
Expand Down Expand Up @@ -67,31 +66,50 @@ export function SetEvmWalletClickRef(props: SetEvmWalletClickRefProps) {
jumpTo(ViewRoutes.EVM_CONNECTING);
};

const jumpToWalletConnectView = () => {
jumpTo(ViewRoutes.EVM_CONNECT_WITH_WALLET_CONNECT);
};

disconnect();

clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
if (isWalletConnect(connector.id)) {
if (wallet.showQRCode) {
jumpToQRCodeView();
if (isTMA()) {
// 1. TMA
if (isMobile()) {
jumpToWalletConnectView();
} else {
wcModal.onOpen();
jumpToQRCodeView();
}
} else if (!wallet.isInstalled()) {
if (mobile) {
} else if (isMobile()) {
// 2. mobile
if (isWalletConnect(walletId)) {
wcModal.onOpen();
} else if (wallet.useWalletConnect) {
jumpToWalletConnectView();
} else if (wallet.isInstalled()) {
jumpToConnectingView();
} else {
const deepLink = wallet.getDeepLink?.();
if (deepLink) {
window.open(deepLink, '_self', 'noopener noreferrer');
} else {
eventConfig.onError?.(new Error('Not supported wallet'), 'Not supported wallet');
}
}
} else {
// 3. pc
if (isWalletConnect(walletId)) {
if (wallet.showQRCode) {
jumpToQRCodeView();
} else {
wcModal.onOpen();
}
} else if (wallet.showQRCode) {
jumpToQRCodeView();
} else {
jumpToConnectingView();
}
} else {
jumpToConnectingView();
}
}, 300);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { useEvmIsConnected } from './useEvmIsConnected';
import { useEventConfig, useLogger } from '@/core/providers/WalletKitProvider/context';
import { evmCommonErrorHandler } from '../utils/evmCommonErrorHandler';
import { getEvmGlobalData } from '../globalData';
import { ConnectErrorType } from 'wagmi/actions';

let timer: any;

export function useQRCodeUri() {
interface UseWalletConnectUriProps {
onError?: (error: ConnectErrorType) => void;
}

export function useWalletConnectUri(props?: UseWalletConnectUriProps) {
const { connectAsync } = useConnect();

const eventConfig = useEventConfig();
Expand Down Expand Up @@ -38,6 +43,8 @@ export function useQRCodeUri() {
clearTimeout(timer);

timer = setTimeout(() => {
props?.onError?.(error);

if (error?.code === 4001) {
evmCommonErrorHandler({
log,
Expand Down
Loading

0 comments on commit fcb528a

Please sign in to comment.