Skip to content

Commit 84b51fb

Browse files
committed
feat(farcaster): init draft for supporting farcaster login
1 parent c73b22c commit 84b51fb

File tree

18 files changed

+629
-6
lines changed

18 files changed

+629
-6
lines changed

Diff for: packages/farcaster/.fatherrc.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { defineConfig } from 'father';
2+
3+
export default defineConfig({
4+
extends: '../../.fatherrc.base.ts',
5+
});

Diff for: packages/farcaster/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# TODO

Diff for: packages/farcaster/package.json

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"name": "@ant-design/web3-farcaster",
3+
"version": "1.0.0",
4+
"main": "dist/lib/index.js",
5+
"module": "dist/esm/index.js",
6+
"typings": "dist/esm/index.d.ts",
7+
"exports": {
8+
"import": "./dist/esm/index.js",
9+
"require": "./dist/lib/index.js",
10+
"types": "./dist/esm/index.d.ts"
11+
},
12+
"sideEffects": false,
13+
"files": [
14+
"dist",
15+
"CHANGELOG.md",
16+
"README.md"
17+
],
18+
"keywords": [
19+
"ant",
20+
"component",
21+
"components",
22+
"design",
23+
"framework",
24+
"frontend",
25+
"react",
26+
"react-component",
27+
"ui",
28+
"web3",
29+
"farcaster"
30+
],
31+
"homepage": "https://web3.ant.design",
32+
"bugs": {
33+
"url": "https://github.com/ant-design/ant-design-web3/issues"
34+
},
35+
"repository": {
36+
"type": "git",
37+
"url": "https://github.com/ant-design/ant-design-web3"
38+
},
39+
"scripts": {
40+
"dev": "father dev",
41+
"build": "father build"
42+
},
43+
"dependencies": {
44+
"@farcaster/auth-client": "^0.1.1",
45+
"@farcaster/auth-kit": "^0.3.1"
46+
},
47+
"devDependencies": {
48+
"father": "^4.4.4",
49+
"typescript": "^5.4.5"
50+
},
51+
"publishConfig": {
52+
"registry": "https://registry.npmjs.org",
53+
"access": "public"
54+
},
55+
"browserslist": [
56+
"last 2 versions",
57+
"Firefox ESR",
58+
"> 1%",
59+
"ie >= 11"
60+
]
61+
}

Diff for: packages/farcaster/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './provider';
2+
export { QRCode } from '@farcaster/auth-kit';

Diff for: packages/farcaster/src/provider/__tests__/utils.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { FC } from 'react';
2+
import { render } from '@testing-library/react';
3+
4+
type RenderResult = ReturnType<typeof render>;
5+
type RenderWithUtils = RenderResult & {
6+
selector: <T extends Element = Element>(selector: string) => T | null;
7+
selectors: <T extends Element = Element>(selector: string) => NodeListOf<T>;
8+
};
9+
type XRender = (Comp: FC, options?: Parameters<typeof render>[1]) => RenderWithUtils;
10+
11+
export const xrender: XRender = (Comp, options) => {
12+
const { baseElement, ...others } = render(<Comp />, options);
13+
return {
14+
baseElement,
15+
...others,
16+
selector: (selector) => baseElement.querySelector(selector),
17+
selectors: (selector) => baseElement.querySelectorAll(selector),
18+
};
19+
};

Diff for: packages/farcaster/src/provider/index.tsx

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, { useCallback, useEffect } from 'react';
2+
import type { Provider } from '@farcaster/auth-client';
3+
import { AuthKitProvider, useSignIn, type UseSignInArgs } from '@farcaster/auth-kit';
4+
5+
import '@farcaster/auth-kit/styles.css';
6+
7+
// declares locally in '@farcaster/auth-kit', but it is not exported
8+
interface AuthKitConfig {
9+
relay?: string;
10+
domain?: string;
11+
siweUri?: string;
12+
rpcUrl?: string;
13+
redirectUrl?: string;
14+
version?: string;
15+
provider?: Provider;
16+
}
17+
18+
interface IFarcasterContext extends Partial<ReturnType<typeof useSignIn>> {
19+
farcasterSupported: boolean;
20+
farcasterLogin: () => void;
21+
}
22+
23+
const FarcasterContext = React.createContext<IFarcasterContext>({
24+
farcasterSupported: false,
25+
farcasterLogin: () => {},
26+
});
27+
28+
const FarcasterConfigProvider: React.FC<React.PropsWithChildren<UseSignInArgs>> = ({
29+
children,
30+
...signInArgs
31+
}) => {
32+
const signInState = useSignIn(signInArgs);
33+
const { isError, reconnect, signIn, channelToken, connect } = signInState;
34+
35+
const farcasterLogin = useCallback(() => {
36+
if (isError) {
37+
reconnect();
38+
}
39+
signIn();
40+
}, [isError, reconnect, signIn]);
41+
42+
useEffect(() => {
43+
if (!channelToken) {
44+
connect();
45+
}
46+
}, [channelToken, connect]);
47+
48+
return (
49+
<FarcasterContext.Provider value={{ farcasterSupported: true, farcasterLogin, ...signInState }}>
50+
{children}
51+
</FarcasterContext.Provider>
52+
);
53+
};
54+
55+
interface Web3FarcasterProviderProps extends UseSignInArgs {
56+
config?: AuthKitConfig;
57+
}
58+
59+
export const useFarcaster = () => {
60+
const farcaster = React.useContext(FarcasterContext);
61+
return farcaster;
62+
};
63+
64+
export const FarcasterWeb3ConfigProvider: React.FC<
65+
React.PropsWithChildren<Web3FarcasterProviderProps>
66+
> = ({ children, config = {}, ...signInArgs }) => {
67+
return (
68+
<AuthKitProvider config={config}>
69+
<FarcasterConfigProvider {...signInArgs}>{children}</FarcasterConfigProvider>
70+
</AuthKitProvider>
71+
);
72+
};

Diff for: packages/farcaster/tsconfig.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"include": ["src", "global.d.ts"]
4+
}

Diff for: packages/web3/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@ant-design/web3-assets": "workspace:*",
4747
"@ant-design/web3-common": "workspace:*",
4848
"@ant-design/web3-icons": "workspace:*",
49+
"@ant-design/web3-farcaster": "workspace:*",
4950
"@ctrl/tinycolor": "^4.1.0",
5051
"@inline-svg-unique-id/react": "^1.2.3",
5152
"antd": "^5.17.3",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import { QRCode, useFarcaster } from '@ant-design/web3-farcaster';
3+
4+
import { connectModalContext } from '../context';
5+
import MainPanelHeader from './MainPanelHeader';
6+
7+
const FarcasterCard = () => {
8+
const { url, error, isError } = useFarcaster();
9+
const { prefixCls } = React.useContext(connectModalContext);
10+
11+
return (
12+
<>
13+
<div className={`${prefixCls}-qr-code-container`}>
14+
<MainPanelHeader title="Sign in with Farcaster" />
15+
<div className={`${prefixCls}-qr-code-box`}>
16+
{isError ? (
17+
<>
18+
<div>Error</div>
19+
<div>{error?.message ?? 'Unknown error, please try again.'}</div>
20+
</>
21+
) : url ? (
22+
<QRCode uri={url} size={300} logoSize={28} logoMargin={16} />
23+
) : null}
24+
</div>
25+
<div className={`${prefixCls}-qr-code-tips`}>
26+
<div>Scan with your phone&apos;s camera to continue.</div>
27+
</div>
28+
</div>
29+
</>
30+
);
31+
};
32+
33+
export default FarcasterCard;

Diff for: packages/web3/src/connect-modal/components/MainPanel.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { useContext } from 'react';
33
import { connectModalContext } from '../context';
44
import type { ConnectModalProps } from '../interface';
55
import DefaultGuidePanel from './DefaultGuidePanel';
6+
import FarcasterCard from './FarcasterCard';
67
import LinkPanel from './LinkPanel';
78
import QrCode from './QrCode';
89
import WalletCard from './WalletCard';
@@ -28,6 +29,7 @@ const MainPanel: React.FC<MainPanelProps> = (props) => {
2829
{panelRoute === 'downloadQrCode' && selectedWallet ? (
2930
<QrCode wallet={selectedWallet} simple={simple} download />
3031
) : null}
32+
{panelRoute === 'farcaster' ? <FarcasterCard /> : null}
3133
</div>
3234
);
3335
};

Diff for: packages/web3/src/connect-modal/components/WalletList.tsx

+52-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { forwardRef, useContext, useImperativeHandle, useMemo } from 'react';
22
import { QrcodeOutlined } from '@ant-design/icons';
3+
import { useFarcaster } from '@ant-design/web3-farcaster';
34
import { Button, List, Space, Typography } from 'antd';
45
import classNames from 'classnames';
56

@@ -69,7 +70,52 @@ const WalletList = forwardRef<ConnectModalActionType, WalletListProps>((props, r
6970
selectWallet,
7071
};
7172
});
72-
const renderContent = (params?: { group?: string }) => {
73+
74+
const { farcasterSupported, farcasterLogin } = useFarcaster();
75+
76+
const renderFarcasterContent = () => {
77+
return farcasterSupported ? (
78+
<List
79+
itemLayout="horizontal"
80+
dataSource={[
81+
{
82+
key: 'farcaster',
83+
name: 'Farcaster',
84+
},
85+
]}
86+
rowKey="key"
87+
renderItem={(item) => (
88+
<List.Item
89+
className={classNames(`${prefixCls}-wallet-item`)}
90+
onClick={() => {
91+
farcasterLogin();
92+
updatePanelRoute('farcaster', true);
93+
}}
94+
>
95+
<div className={`${prefixCls}-content`}>
96+
<div>图标</div>
97+
<Typography.Text ellipsis={{ tooltip: true }} className={`${prefixCls}-name`}>
98+
{item.name}
99+
</Typography.Text>
100+
</div>
101+
<Button
102+
size="small"
103+
className={`${prefixCls}-qr-btn`}
104+
onClick={(e) => {
105+
e.stopPropagation();
106+
farcasterLogin();
107+
updatePanelRoute('farcaster', true);
108+
}}
109+
>
110+
<QrcodeOutlined />
111+
</Button>
112+
</List.Item>
113+
)}
114+
/>
115+
) : null;
116+
};
117+
118+
const renderWalletsContent = (params?: { group?: string }) => {
73119
const { group } = params || {};
74120
return (
75121
<List<Wallet>
@@ -123,20 +169,23 @@ const WalletList = forwardRef<ConnectModalActionType, WalletListProps>((props, r
123169

124170
return (
125171
<div className={`${prefixCls}-wallet-list`}>
172+
<div className={`${prefixCls}-group`}>
173+
<div className={`${prefixCls}-group-content`}>{renderFarcasterContent()}</div>
174+
</div>
126175
{internalGroup ? (
127176
groupKeys.map((group) => (
128177
<div className={`${prefixCls}-group`} key={group}>
129178
<div className={`${prefixCls}-group-title`}>{group}</div>
130179
<div className={`${prefixCls}-group-content`}>
131-
{renderContent({
180+
{renderWalletsContent({
132181
group,
133182
})}
134183
</div>
135184
</div>
136185
))
137186
) : (
138187
<div className={`${prefixCls}-group`}>
139-
<div className={`${prefixCls}-group-content`}>{renderContent()}</div>
188+
<div className={`${prefixCls}-group-content`}>{renderWalletsContent()}</div>
140189
</div>
141190
)}
142191
</div>

Diff for: packages/web3/src/connect-modal/demos/basic.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { ConnectModal, ConnectModalProps } from '@ant-design/web3';
2+
import { ConnectModal, type ConnectModalProps } from '@ant-design/web3';
33
import {
44
metadata_MetaMask,
55
metadata_MobileConnect,

Diff for: packages/web3/src/connect-modal/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const ConnectModal: React.FC<ConnectModalProps> & {
3838
onCancel={(e) => {
3939
onCancel?.(e);
4040
}}
41+
destroyOnClose
4142
>
4243
<ModalPanel {...props} />
4344
</Modal>,

Diff for: packages/web3/src/connect-modal/interface.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,11 @@ export type ConnectModalProps = ModalProps &
144144
connecting?: boolean;
145145
};
146146

147-
export type PanelRoute = 'init' | 'guide' | 'wallet' | 'qrCode' | 'downloadQrCode' | 'link';
147+
export type PanelRoute =
148+
| 'init'
149+
| 'guide'
150+
| 'wallet'
151+
| 'qrCode'
152+
| 'downloadQrCode'
153+
| 'link'
154+
| 'farcaster';

Diff for: packages/web3/src/connector/connector.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import { ConnectModal, type ConnectModalActionType } from '@ant-design/web3';
33
import type { Chain, ConnectOptions, ConnectorTriggerProps, Wallet } from '@ant-design/web3-common';
4+
import { useFarcaster } from '@ant-design/web3-farcaster';
45
import { message } from 'antd';
56

67
import useProvider from '../hooks/useProvider';
@@ -34,6 +35,13 @@ export const Connector: React.FC<ConnectorProps> = (props) => {
3435
const actionRef = React.useRef<ConnectModalActionType>();
3536
const [messageApi, contextHolder] = message.useMessage();
3637

38+
// close modal when login farcaster success
39+
const { isSuccess } = useFarcaster();
40+
useEffect(() => {
41+
if (!isSuccess) return;
42+
setOpen(false);
43+
}, [isSuccess]);
44+
3745
const connectWallet = async (wallet?: Wallet, options?: ConnectOptions) => {
3846
onConnect?.();
3947
try {

0 commit comments

Comments
 (0)