-
Notifications
You must be signed in to change notification settings - Fork 185
feat: 新增icp链-plug钱包链接 #1556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: 新增icp链-plug钱包链接 #1556
Conversation
🦋 Changeset detectedLatest commit: acf7a64 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Someone is attempting to deploy a commit to the pro-components Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthrough新增 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User as 用户
participant UI as ConnectButton
participant IcpProvider as IcpWeb3ConfigProvider
participant Factory as IcpWalletFactory
participant Wallet as 钱包 (Plug/Infinity)
participant Ext as 浏览器扩展
User->>UI: 点击连接
UI->>IcpProvider: 调用 connect()
IcpProvider->>Factory: 选择/创建可用钱包实例
Factory->>Wallet: create()
Wallet->>Wallet: 检查 installed
alt 已安装
IcpProvider->>Wallet: 调用 connect()
Wallet->>Ext: requestConnect()
Ext-->>Wallet: 用户授权
Wallet->>Ext: getPrincipal()
Ext-->>Wallet: 返回 principal
Wallet-->>IcpProvider: 返回 principal
IcpProvider->>IcpProvider: 更新 principal/account/balance
IcpProvider-->>UI: 连接成功
else 未安装
Wallet-->>IcpProvider: 返回未安装
IcpProvider-->>UI: 提示或回退逻辑
end
Estimated code review effort🎯 4 (复杂) | ⏱️ ~60 分钟
诗歌
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @wss-mz-web3, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求通过引入一个新的 Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次 PR 新增了对 ICP 链和 Plug 钱包的支持,整体实现良好。代码结构清晰,并提供了相应的演示和文档。我提出了一些关于性能优化、代码复用和 React Hooks 最佳实践的建议,希望能帮助提升代码质量和可维护性。请查看具体的评论。
| const handleConnect = async () => { | ||
| try { | ||
| if (!installed) { | ||
| Modal.info({ | ||
| title: `未检测到 ${walletName} 钱包`, | ||
| content: ( | ||
| <> | ||
| <p>当前浏览器环境未检测到 {walletName} 钱包扩展。</p> | ||
| <p>请先安装该钱包并完成初始化,再刷新页面重试。</p> | ||
| </> | ||
| ), | ||
| okText: '我知道了', | ||
| }); | ||
| return; | ||
| } | ||
| await connect(); | ||
| } catch (error: any) { | ||
| messageApi.error(error?.message ?? `连接 ${walletName} 钱包失败,请检查插件是否安装。`); | ||
| } | ||
| }; | ||
|
|
||
| const handleDisconnect = async () => { | ||
| try { | ||
| await disconnect(); | ||
| } catch (error: any) { | ||
| messageApi.error(error?.message ?? `断开 ${walletName} 钱包失败,请重试。`); | ||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (autoConnect) { | ||
| void (async () => { | ||
| await handleConnect(); | ||
| onAutoConnectConsumed(); | ||
| })(); | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [autoConnect, walletType]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useEffect 钩子禁用 exhaustive-deps 规则,这可能掩盖了潜在的 bug,即 handleConnect 和 onAutoConnectConsumed 未包含在依赖项数组中。handleConnect 函数在每次渲染时都会重新创建,并且依赖于 installed、connect 和 walletName。为了解决这个问题,应该使用 useCallback 包装 handleConnect 和 handleDisconnect,并将它们包含在 useEffect 的依赖项数组中。这可以确保 effect 仅在其真正的依赖项发生变化时才运行,并遵循 React hooks 的最佳实践。
const handleConnect = React.useCallback(async () => {
try {
if (!installed) {
Modal.info({
title: `未检测到 ${walletName} 钱包`,
content: (
<>
<p>当前浏览器环境未检测到 ${walletName} 钱包扩展。</p>
<p>请先安装该钱包并完成初始化,再刷新页面重试。</p>
</>
),
okText: '我知道了',
});
return;
}
await connect();
} catch (error: any) {
messageApi.error(error?.message ?? `连接 ${walletName} 钱包失败,请检查插件是否安装。`);
}
}, [installed, connect, walletName, messageApi]);
const handleDisconnect = React.useCallback(async () => {
try {
await disconnect();
} catch (error: any) {
messageApi.error(error?.message ?? `断开 ${walletName} 钱包失败,请重试。`);
}
}, [disconnect, walletName, messageApi]);
useEffect(() => {
if (autoConnect) {
void (async () => {
await handleConnect();
onAutoConnectConsumed();
})();
}
}, [autoConnect, handleConnect, onAutoConnectConsumed]);
| const connect = async () => { | ||
| setConnecting(true); | ||
| try { | ||
| await wallet.connect(); | ||
| const p = await wallet.getPrincipal(); | ||
| setPrincipal(p); | ||
| } finally { | ||
| setConnecting(false); | ||
| } | ||
| }; | ||
|
|
||
| const disconnect = async () => { | ||
| await wallet.disconnect(); | ||
| setPrincipal(null); | ||
| }; | ||
|
|
||
| const value: IcpContextValue = { | ||
| wallet, | ||
| principal, | ||
| connecting, | ||
| connect, | ||
| disconnect, | ||
| installed: wallet.installed, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
connect 和 disconnect 函数以及传递给 IcpContext.Provider 的 value 对象在每次渲染时都会重新创建。这可能导致消费该上下文的组件发生不必要的重新渲染。为了优化性能,建议使用 useCallback 包装 connect 和 disconnect 函数,并使用 useMemo 包装 value 对象。
const connect = React.useCallback(async () => {
setConnecting(true);
try {
await wallet.connect();
const p = await wallet.getPrincipal();
setPrincipal(p);
} finally {
setConnecting(false);
}
}, [wallet]);
const disconnect = React.useCallback(async () => {
await wallet.disconnect();
setPrincipal(null);
}, [wallet]);
const value = React.useMemo<IcpContextValue>(() => ({
wallet,
principal,
connecting,
connect,
disconnect,
installed: wallet.installed,
}), [wallet, principal, connecting, connect, disconnect]);
| export function createPlugWallet(): IcpWallet { | ||
| const getInstalled = () => isPlugInstalled(); | ||
|
|
||
| return { | ||
| id: 'plug', | ||
| name: 'Plug', | ||
| icon: null, | ||
|
|
||
| get installed() { | ||
| return getInstalled(); | ||
| }, | ||
|
|
||
| async connect() { | ||
| const plug = getPlug(); | ||
| if (!plug) { | ||
| // 不抛异常,由上层根据 installed 提示安装钱包 | ||
| return; | ||
| } | ||
| await plug.requestConnect(); | ||
| }, | ||
|
|
||
| async disconnect() { | ||
| const plug = getPlug(); | ||
| if (!plug) { | ||
| return; | ||
| } | ||
| await plug.disconnect(); | ||
| }, | ||
|
|
||
| async isConnected() { | ||
| const plug = getPlug(); | ||
| if (!plug) { | ||
| return false; | ||
| } | ||
| return plug.isConnected(); | ||
| }, | ||
|
|
||
| async getPrincipal() { | ||
| const plug = getPlug(); | ||
| if (!plug) { | ||
| return null; | ||
| } | ||
| try { | ||
| const principal = await plug.getPrincipal(); | ||
| return principal.toText(); | ||
| } catch { | ||
| return null; | ||
| } | ||
| }, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createPlugWallet 的实现与 packages/icp/src/wallets/infinity.tsx 中的 createInfinityWallet 非常相似。这种代码重复可以通过创建一个通用的钱包工厂函数来减少,从而提高可维护性,并使将来添加更多钱包变得更容易。
例如,您可以在一个共享文件中创建此函数:
function createIcpWalletImplementation(
id: 'plug' | 'infinity',
name: string,
getWalletObject: () => any | undefined
): IcpWallet {
const isInstalled = () => !!getWalletObject();
return {
id,
name,
icon: null,
get installed() {
return isInstalled();
},
async connect() {
const wallet = getWalletObject();
if (!wallet) return;
await wallet.requestConnect();
},
async disconnect() {
const wallet = getWalletObject();
if (!wallet) return;
await wallet.disconnect();
},
async isConnected() {
const wallet = getWalletObject();
if (!wallet) return false;
return wallet.isConnected();
},
async getPrincipal() {
const wallet = getWalletObject();
if (!wallet) return null;
try {
const principal = await wallet.getPrincipal();
return principal.toText();
} catch {
return null;
}
},
};
}
// 在 plug.tsx 中
export function createPlugWallet(): IcpWallet {
return createIcpWalletImplementation('plug', 'Plug', getPlug);
}
// 在 infinity.tsx 中
export function createInfinityWallet(): IcpWallet {
return createIcpWalletImplementation('infinity', 'Infinity', getInfinity);
}| ); | ||
|
|
||
| const handleWalletSelected = async (wallet: Wallet) => { | ||
| const nextType = (wallet.key as WalletType) ?? 'plug'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (12)
packages/web3/src/icp/index.zh-CN.md (1)
1-31: 中文文档结构清晰,说明与当前实现匹配整体结构(介绍 / 何时使用 / 代码演示)和其它链文档风格一致,也明确说明当前仅内置 Plug 钱包、未来可扩展更多 ICP 钱包,和当前对外 API(
IcpWeb3ConfigProvider+ConnectButton+ 基础 demo)是对齐的。后续如果将 Infinity 或其他 ICP 钱包真正接入对外能力,记得同步在这里补充一句支持列表,避免和代码实现脱节。packages/icp/src/index.ts (1)
1-3: 对外导出保持最小 API,确认是否刻意不暴露 Infinity 钱包当前仅从根入口导出了:
./icp-provider/context(Provider / hooks)./wallets/plug(Plug 钱包工厂 & 工具)./wallets/types(类型)这在“只对外宣称支持 Plug”这一阶段是合理的最小 API 设计。由于代码中已经存在
wallets/infinity实现和IcpWalletType = 'plug' | 'infinity',建议团队内部确认一下:
- 如果短期内不打算对外开放 Infinity 工具函数,保持现在的导出集合即可;
- 如果希望让高级用户自行接入 Infinity,可在后续 PR 中补充一行
export * from './wallets/infinity';并同步文档。当前实现本身没有问题,只是 API 取舍需要对齐一下对外支持范围。
packages/web3/src/icp/index.md (1)
1-28: 英文文档与中文版基本对齐,描述清晰英文版同样聚焦于 “ICP 链 + Plug 钱包最小接入”,并展示通过
IcpWeb3ConfigProvider+ConnectButton接入的方式,和中文文档、代码实现是一致的。后续如果正式开放 Infinity 或其它 ICP 钱包支持,记得同时更新中英文两份文档中的“currently ships with Plug wallet support”/“目前内置支持 Plug 钱包”等表述,以免文档与实际能力不一致。packages/icp/package.json (1)
1-63: ICP 包 manifest 基本与其它链包保持一致
main/module/typings与exports均指向dist下同一路径,看起来是沿用现有链包的约定;dependencies仅依赖@ant-design/web3-common(workspace),devDependencies中声明了father和typescript,以及常规的files、sideEffects、publishConfig配置,整体规范。建议在合并前做两点确认:
- Father 配置的构建输出目录与这里的
dist/lib、dist/esm保持一致,避免发布后入口指向不存在的文件;- 如果后续正式支持 Infinity 并对外宣传,可以顺带在
keywords中补充相关关键词(目前只含有plug)。除此之外,这个 manifest 本身没有明显问题。
packages/icp/src/wallets/plug.tsx (2)
23-42: 建议为 connect 失败场景提供反馈机制当前
connect()在钱包未安装时静默返回,虽然注释说明了由上层处理,但如果plug.requestConnect()抛出异常,错误会向上传播而未被捕获。建议添加 try-catch 以保持一致的错误处理策略。async connect() { const plug = getPlug(); if (!plug) { // 不抛异常,由上层根据 installed 提示安装钱包 return; } - await plug.requestConnect(); + try { + await plug.requestConnect(); + } catch (error) { + console.error('Plug wallet connect failed:', error); + throw error; + } },
26-33: 考虑添加钱包图标
icon当前设置为null。为了提升用户体验,建议添加 Plug 钱包的官方图标,这样在钱包选择列表中会有更好的视觉效果。packages/icp/src/icp-provider/context.tsx (3)
49-58: connect 函数缺少错误处理
connect函数调用wallet.connect()和wallet.getPrincipal()时没有 catch 错误,异常会直接向上抛出。虽然上层 demo 有 try-catch,但作为通用 Provider,建议在此处也添加错误处理或至少记录日志。const connect = async () => { setConnecting(true); try { await wallet.connect(); const p = await wallet.getPrincipal(); setPrincipal(p); + } catch (error) { + console.error('ICP wallet connect failed:', error); + throw error; } finally { setConnecting(false); } };
49-63: 建议使用 useCallback 优化 connect/disconnect
connect和disconnect函数在每次渲染时都会重新创建,可能导致依赖这些函数的子组件不必要地重新渲染。- const connect = async () => { + const connect = useCallback(async () => { setConnecting(true); try { await wallet.connect(); const p = await wallet.getPrincipal(); setPrincipal(p); } finally { setConnecting(false); } - }; + }, [wallet]); - const disconnect = async () => { + const disconnect = useCallback(async () => { await wallet.disconnect(); setPrincipal(null); - }; + }, [wallet]);
65-74: Context value 对象建议 memoize每次渲染都会创建新的
value对象,可能导致消费该 Context 的组件不必要地重新渲染。- const value: IcpContextValue = { - wallet, - principal, - connecting, - connect, - disconnect, - installed: wallet.installed, - }; + const value: IcpContextValue = useMemo( + () => ({ + wallet, + principal, + connecting, + connect, + disconnect, + installed: wallet.installed, + }), + [wallet, principal, connecting, connect, disconnect] + );packages/web3/src/icp/demos/basic.tsx (3)
18-26: checkInstalled 类型定义可以简化
isPlugInstalled()是同步函数返回boolean,但checkInstalled被定义为Promise<boolean>。虽然async () => isPlugInstalled()可以工作,但类型定义可以更精确。type WalletMeta = { name: string; remark: string; group: string; color: string; - checkInstalled?: () => Promise<boolean>; + checkInstalled?: () => boolean | Promise<boolean>; };
46-59: 重复调用 checkInstalled 可以优化
hasExtensionInstalled和hasWalletReady使用了几乎相同的逻辑且都调用meta.checkInstalled?.()。可以考虑复用同一个函数引用。const walletList: Wallet[] = (Object.keys(walletMetaMap) as WalletType[]).map((type) => { const meta = walletMetaMap[type]; + const checkReady = meta.checkInstalled + ? async () => !!(await meta.checkInstalled?.()) + : undefined; return { key: type, name: meta.name, remark: meta.remark, icon: <WalletIcon color={meta.color} label={meta.name.slice(0, 1)} />, group: meta.group, - hasExtensionInstalled: meta.checkInstalled - ? async () => !!(await meta.checkInstalled?.()) - : undefined, - hasWalletReady: meta.checkInstalled ? async () => !!(await meta.checkInstalled?.()) : undefined, + hasExtensionInstalled: checkReady, + hasWalletReady: checkReady, }; });
158-180: handleWalletSelected 中的安装检查与 IcpConnectButton 重复钱包安装状态在
handleWalletSelected和IcpConnectButton.handleConnect中都进行了检查。考虑到用户体验,在选择钱包时检查是合理的,但建议统一检查逻辑的来源(使用 context 中的installed状态)。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
packages/icp/package.json(1 hunks)packages/icp/src/icp-provider/context.tsx(1 hunks)packages/icp/src/index.ts(1 hunks)packages/icp/src/wallets/infinity.tsx(1 hunks)packages/icp/src/wallets/plug.tsx(1 hunks)packages/icp/src/wallets/types.ts(1 hunks)packages/icp/tsconfig.json(1 hunks)packages/web3/src/icp/demos/basic.tsx(1 hunks)packages/web3/src/icp/index.md(1 hunks)packages/web3/src/icp/index.zh-CN.md(1 hunks)tsconfig.base.json(1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: This is a multi-package React project built on Ant Design component library, designed to provide Web3 projects with wallet connection capabilities and DApp UI components
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: Applies to packages/web3/*/index.{md,zh-CN.md} : Documentation files should be available as `packages/web3/*/index.md` and `packages/web3/*/index.zh-CN.md` for component documentation and `packages/web3/ethereum` for wagmi package documentation
Applied to files:
packages/web3/src/icp/index.mdpackages/web3/src/icp/index.zh-CN.mdpackages/icp/package.json
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: This is a multi-package React project built on Ant Design component library, designed to provide Web3 projects with wallet connection capabilities and DApp UI components
Applied to files:
packages/web3/src/icp/index.mdpackages/web3/src/icp/index.zh-CN.mdpackages/icp/src/wallets/plug.tsxpackages/icp/src/icp-provider/context.tsxpackages/icp/src/wallets/infinity.tsxpackages/icp/package.jsonpackages/web3/src/icp/demos/basic.tsxpackages/icp/src/wallets/types.tstsconfig.base.json
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: UI components are located under `packages/web3`, with other packages containing adapters for different blockchains or foundational packages
Applied to files:
packages/icp/package.jsontsconfig.base.json
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: Applies to packages/web3/*/demos/** : Demo components should be located in `packages/web3/*/demos` directories
Applied to files:
packages/web3/src/icp/demos/basic.tsxtsconfig.base.json
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: Applies to **/style/index.{ts,tsx} : Styling implementation should be based on `ant-design/cssinjs` and utilize Ant Design's Design Tokens for custom styling support
Applied to files:
tsconfig.base.json
🧬 Code graph analysis (3)
packages/icp/src/wallets/plug.tsx (1)
packages/icp/src/wallets/types.ts (1)
IcpWallet(3-12)
packages/icp/src/wallets/infinity.tsx (1)
packages/icp/src/wallets/types.ts (1)
IcpWallet(3-12)
packages/web3/src/icp/demos/basic.tsx (2)
packages/icp/src/wallets/plug.tsx (4)
isPlugInstalled(21-21)installed(31-33)connect(35-42)disconnect(44-50)packages/icp/src/icp-provider/context.tsx (2)
useIcpWallet(25-33)IcpWeb3ConfigProvider(35-75)
🔇 Additional comments (6)
packages/icp/tsconfig.json (1)
1-4: ICP 子包 tsconfig 配置整体合理继承根目录
tsconfig.base.json,并仅包含src与global.d.ts,和多包结构的常见做法一致,后续如果有测试文件夹再按需补充即可。可以在本包目录跑一遍构建/类型检查,确认不会有路径或类型解析问题。tsconfig.base.json (1)
37-38: 新增路径别名与包目录结构一致
"@ant-design/web3-icp": ["./packages/icp/src/"]与新包目录匹配,也保持了和其他链包一致的 alias 约定,便于在 docs/demos 中直接从别名导入。建议在根目录跑一遍 TS 构建或现有 lint 流程,确认别名解析正常(例如在 demo 中用该 alias 导入时无报错)。packages/icp/src/wallets/types.ts (1)
1-14: IcpWallet 类型设计合理,注意与其它链钱包接口保持一致
IcpWallet抽象了常见的钱包能力(安装状态、连接/断开、连接状态查询、获取 principal),并通过IcpWalletType预留了plug | infinity两种类型,整体设计清晰、易于扩展。建议在合并前简单对比一下其它链(如 EVM、Ton 等)对应的钱包接口,确保字段命名与行为语义尽量统一,方便上层做统一处理。packages/icp/src/wallets/plug.tsx (2)
18-21: SSR 安全检查实现正确
getPlug函数正确处理了服务端渲染场景,避免了window未定义的问题。
60-71: getPrincipal 错误处理良好使用 try-catch 包裹异步调用并返回
null作为降级方案,这是合理的防御性编程。packages/web3/src/icp/demos/basic.tsx (1)
144-203: Demo 组件整体结构清晰Demo 正确展示了 ICP 钱包连接流程,包括钱包选择、安装检测、连接/断开等功能。使用
IcpWeb3ConfigProvider包裹并传递walletType的模式符合预期设计。
| const wallet = useMemo<IcpWallet>(() => { | ||
| if (walletType === 'infinity') { | ||
| return createInfinityWallet(); | ||
| } | ||
| return createPlugWallet(); | ||
| }, [walletType]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
钱包类型切换时需要重置状态
当 walletType 改变时,wallet 会通过 useMemo 重新创建,但 principal 状态会保留之前钱包的值。这可能导致 UI 显示错误的连接状态。
+ // 当钱包类型改变时重置状态
+ useEffect(() => {
+ setPrincipal(null);
+ }, [walletType]);
+
const wallet = useMemo<IcpWallet>(() => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const wallet = useMemo<IcpWallet>(() => { | |
| if (walletType === 'infinity') { | |
| return createInfinityWallet(); | |
| } | |
| return createPlugWallet(); | |
| }, [walletType]); | |
| // 当钱包类型改变时重置状态 | |
| useEffect(() => { | |
| setPrincipal(null); | |
| }, [walletType]); | |
| const wallet = useMemo<IcpWallet>(() => { | |
| if (walletType === 'infinity') { | |
| return createInfinityWallet(); | |
| } | |
| return createPlugWallet(); | |
| }, [walletType]); |
🤖 Prompt for AI Agents
In packages/icp/src/icp-provider/context.tsx around lines 42 to 47, the useMemo
recreates the wallet when walletType changes but the principal state remains
from the previous wallet causing stale connection UI; update the provider so
that whenever walletType (or the derived wallet) changes you reset/clear the
principal state (and any related connection flags) and re-run any wallet
initialization/connection logic — e.g., add an effect that watches walletType or
wallet and sets principal to null/undefined (and performs any necessary
cleanup/disconnect of the old wallet) before creating/connecting the new wallet.
| // NOTE: 这里假设 Infinity 钱包通过 window.ic.infinityWallet 注入, | ||
| // 具体字段请根据官方文档调整。 | ||
| declare global { | ||
| interface Window { | ||
| ic?: { | ||
| infinityWallet?: { | ||
| isConnected: () => Promise<boolean>; | ||
| requestConnect: (opts?: unknown) => Promise<{ principalId: string }>; | ||
| disconnect: () => Promise<void>; | ||
| getPrincipal: () => Promise<{ toText: () => string }>; | ||
| }; | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| const getInfinity = () => | ||
| typeof window === 'undefined' || typeof window.ic === 'undefined' | ||
| ? undefined | ||
| : window.ic.infinityWallet; | ||
|
|
||
| export const isInfinityInstalled = () => !!getInfinity(); | ||
|
|
||
| export function createInfinityWallet(): IcpWallet { | ||
| const getInstalled = () => isInfinityInstalled(); | ||
|
|
||
| return { | ||
| id: 'infinity', | ||
| name: 'Infinity', | ||
| icon: null as React.ReactNode | null, | ||
|
|
||
| get installed() { | ||
| return getInstalled(); | ||
| }, | ||
|
|
||
| async connect() { | ||
| const wallet = getInfinity(); | ||
| if (!wallet) { | ||
| return; | ||
| } | ||
| await wallet.requestConnect(); | ||
| }, | ||
|
|
||
| async disconnect() { | ||
| const wallet = getInfinity(); | ||
| if (!wallet) { | ||
| return; | ||
| } | ||
| await wallet.disconnect(); | ||
| }, | ||
|
|
||
| async isConnected() { | ||
| const wallet = getInfinity(); | ||
| if (!wallet) { | ||
| return false; | ||
| } | ||
| return wallet.isConnected(); | ||
| }, | ||
|
|
||
| async getPrincipal() { | ||
| const wallet = getInfinity(); | ||
| if (!wallet) { | ||
| return null; | ||
| } | ||
| try { | ||
| const principal = await wallet.getPrincipal(); | ||
| return principal.toText(); | ||
| } catch { | ||
| return null; | ||
| } | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find references to Infinity wallet in the codebase
rg -i "infinity" --type ts --type tsx --type js --type json -B2 -A2Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
# Check the structure of the wallets directory to understand patterns
git ls-files packages/icp/src/wallets/Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
# Look for tests related to Infinity or wallet implementations
fd -e test.ts -e test.tsx -e spec.ts -e spec.tsx | head -20Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
# Check if Infinity is exported from index files
rg "infinity" packages/icp/src/ --type ts --type tsxRepository: ant-design/ant-design-web3
Length of output: 196
🌐 Web query:
Infinity wallet ICP API documentation official
💡 Result:
I couldn't find an official Infinity Wallet "ICP API" reference. Useful official links I found instead:
- Infinity Wallet developer docs (DApp/connect, add-a-dapp, chains/tokens). These are the primary Infinity Wallet docs site. [1]
- Infinity Wallet GitHub (repo / notices). [2]
- Infinity Wallet "DApp Connect" integration guide (developer-facing page). [3]
If you want an ICP-specific wallet API for Internet Computer (ICP) dapps, the common ICP wallets with developer docs are Plug and Stoic (and the Internet Computer docs for ledger/canisters). If you'd like, I can: (A) search for a specific Infinity Wallet ICP page again, (B) fetch Plug/Stoic ICP wallet developer docs, or (C contact Infinity Wallet support pages for confirmation. Which do you prefer?
Sources:
[1] https://docs.infinitywallet.io/
[2] https://github.com/infinitywallet/infinitywallet
[3] https://docs.infinitywallet.io/developers-and-projects/dapp-connect-integration
🌐 Web query:
Infinity wallet window.ic API specification
💡 Result:
Do you mean the Internet Computer "IC provider" API (window.ic) used by wallets like Plug, or Infinity Wallet (infinitywallet.io) for EVM/mobile? I can't find an official "Infinity Wallet window.ic" API specification — Infinity Wallet's repo/docs are deprecated and their site doesn't publish a window.ic spec. [1][2]
If you mean the IC provider API (what dapps use at window.ic.*), refer to Wallets such as Plug which inject window.ic.plug and publish developer docs showing the API methods (requestConnect, agent, requestTransfer, etc.). See Plug developer docs for the API shape and examples. [3][4]
Sources:
- Infinity Wallet (repo / support / deprecation notice). [1]
- Infinity Wallet feature/docs (no window.ic spec). [2]
- Plug Wallet developer docs (example and window.ic.plug API). [3]
- Plug usage examples / forum references to window.ic.plug methods. [4]
Tell me which you meant (Infinity Wallet for IC dapps vs Infinity Wallet desktop/mobile for EVM), and I’ll fetch the exact API spec or relevant docs.
🌐 Web query:
Infinity Wallet Internet Computer ICP support
💡 Result:
Short answer: No — Infinity Wallet does not list Internet Computer (ICP) as a supported chain.
Evidence:
- Infinity Wallet supported-chains list (no ICP / Internet Computer entry). [1]
- DFINITY / Internet Computer documentation lists common ICP wallets (NNS dapp, Plug, Stoic, etc.) — these are the wallets to use for ICP. [2][3]
If you want, I can show how to move ICP into a recommended wallet (Plug/Stoic/NNS) or check again for a recent Infinity Wallet update.
🌐 Web query:
"Infinity Wallet" "Internet Computer" integration
💡 Result:
Short answer: There are two different projects called "Infinity Wallet." One is a multi‑chain desktop wallet (Infinity Wallet.io) that does not list Internet Computer / ICP among its supported chains in its docs. The other is an Internet Computer ecosystem wallet (used with InfinitySwap, NFT marketplaces on ICP, and integrations like Internet Identity / ICRC‑1) and is referenced by IC community sites and tooling (e.g., Artemis adapters). So whether "Infinity Wallet" integrates with Internet Computer depends on which project you mean. Sources: [1][2][3][4].
Sources
- Infinity Wallet (desktop) docs — supported chains and DApp integration (no ICP listed). [1]
- Infinity Wallet (desktop) features / Web3 browser docs. [2]
- IC ecosystem / InfinitySwap / Bitfinity blog referencing Infinity Wallet as an ICP wallet and NFT marketplace integrations. [3]
- Artemis React (ICP wallet adapters) listing Infinity Wallet among Internet Computer wallets. [4]
🌐 Web query:
Artemis React Infinity Wallet ICP adapter API
💡 Result:
Short answer
Artemis is a set of JavaScript/React packages (artemis-web3-adapter + artemis-react) that act as a universal Internet Computer (ICP) wallet adapter/bridge — it exposes a React-friendly API to connect, disconnect, auto‑connect, sign and send calls via multiple ICP wallets (Plug, Stoic, Internet Identity, Infinity Wallet, NFID, etc.). The Infinity Wallet is one of the built‑in connectors supported by Artemis. [1][2][3]
Quick facts / how to get started
- Install the packages: npm i @amerej/artemis-web3-adapter @amerej/artemis-react. [1][2]
- Create an Artemis adapter instance with your IC host/whitelist and use the adapter’s connect/disconnect/sign/send methods (artemis-web3-adapter provides the core API; artemis-react exposes React hooks/components). Example from the adapter README: const artemis = new Artemis({ whitelist: [...], host: 'https://icp0.io' }); then use the adapter or react utilities to connect. [2][1]
- Supported wallets include Infinity Wallet (plus Plug, Stoic, Internet Identity, NFID, MetaMask/MSQ for Safe ICP flows). Use Artemis’ built‑in connector for Infinity Wallet to prompt the user and obtain an identity/agent for canister calls. [1][2]
- For Infinity Wallet-specific DApp integration details (if you need to wire the Infinity connector or follow their DApp Connect flow) consult Infinity Wallet’s DApp Connect docs. [3]
Where to read more / docs
- artemis-react (React utilities / hooks): npm package page / repo. [1]
- artemis-web3-adapter (core adapter, BatchTransact, examples): npm package page / README. [2]
- Infinity Wallet DApp Connect developer docs (integration notes for connecting DApps to Infinity Wallet). [3]
If you want, I can: show a minimal React code example wiring Artemis to Infinity Wallet, or fetch the current Artemis API reference/readme and paste the exact method signatures. Which would you prefer?
🌐 Web query:
InfinitySwap Infinity Wallet window.ic API integration
💡 Result:
Quick summary from docs/search:
- Infinity Wallet provides a DApp integration flow and developer docs for adding a DApp / connecting wallets. See their developer page (Add a DApp) for requirements and process. [1]
- Infinity Wallet has developer docs (custom token, wallet features) and a support/knowledge base you can use for implementation details. [2][3]
- Third‑party wallet connectors (e.g., web3-onboard) already include an Infinity Wallet module you can use to integrate the wallet in a web dApp. That is often the easiest route instead of custom provider plumbing. [4]
- Vezgo and other aggregator APIs list Infinity Wallet as a supported provider if you prefer an API connector rather than direct wallet injection. [5]
I could not find any authoritative documentation that describes a provider object named exactly "window.ic" for Infinity Wallet. Do you mean:
- the Infinity Wallet web3 provider (injected window.ethereum-like object), or
- the Internet Computer (IC) API window.ic, or
- a specific InfinitySwap integration?
Tell me which one you mean and whether you want a code example (web3-onboard, injected provider, or using an API connector); I’ll fetch exact code and steps.
Sources:
[1] Infinity Wallet — Add a DApp / developer page.
[2] Infinity Wallet docs — custom token / support.
[3] Infinity Wallet knowledge base.
[4] Web3-Onboard Infinity Wallet module docs.
[5] Vezgo — Infinity Wallet API support.
🌐 Web query:
Infinity Wallet ICP window.ic provider API specification official documentation
💡 Result:
Short answer: I couldn't find an official "window.ic provider" API specification published by Infinity Wallet. The commonly used Internet Computer "window.ic" provider API is documented by Plug (the IC Provider API) and many dapps use that or the ICPay/ICPay SDK; Infinity Wallet appears to support ICP in various ways but does not publish its own window.ic provider spec. See:
- Plug (IC Provider API / window.ic.plug) developer docs — describes the injected window.ic API used by ICP wallets. [1]
- Infinity Wallet connector (npm/github) — Infinity's connector exposes web3 APIs for EVM (window.ethereum.isInfinityWallet) but there is no equivalent official window.ic provider spec in their public docs/repo. [2][3]
If you want, I can:
- fetch and summarize the Plug window.ic API (methods like requestConnect, createActor, requestTransfer, etc.) with examples.
- keep looking for any newer Infinity Wallet developer docs if you want me to continue searching.
Official Infinity Wallet ICP API specification does not exist; merge this PR only after direct coordination with the Infinity team
This implementation assumes a window.ic.infinityWallet API shape (isConnected, requestConnect, disconnect, getPrincipal returning specific structures) without any official documentation or published specification. Web searches confirm that Infinity Wallet does not currently publish a public window.ic provider specification—while Infinity Wallet may support ICP through various integrations (noted in Artemis React adapters), the exact API surface used here is undocumented.
Required before merging:
- Contact the Infinity Wallet team directly to obtain or confirm the official
window.ic.infinityWalletAPI specification (method names, parameter types, return value structures). - Update the
declare globalinterface to match the official spec. - Add a clear comment in the file stating the spec version this code targets and when it was last verified.
- If the team cannot provide an official spec or this integration is not yet officially supported, either defer this PR or mark Infinity support as experimental with a prominent warning.
Without this verification step, the code risks runtime failures when users with Infinity Wallet encounter method signature mismatches or structural differences from the assumed API.
🤖 Prompt for AI Agents
In packages/icp/src/wallets/infinity.tsx around lines 5 to 75, the code assumes
an undocumented window.ic.infinityWallet API; before merging you must verify the
real API with the Infinity Wallet team, update the TypeScript declare global
types to match the official spec, and add an inline comment stating the spec
version and last-verified date; if the team cannot provide a spec, mark this
integration as experimental (prominent comment and runtime opt-in/feature flag)
and add defensive runtime guards and error handling for unexpected shapes to
avoid crashes.
| import type React from 'react'; | ||
|
|
||
| import type { IcpWallet } from './types'; | ||
|
|
||
| declare global { | ||
| interface Window { | ||
| ic?: { | ||
| plug?: { | ||
| isConnected: () => Promise<boolean>; | ||
| requestConnect: (opts?: unknown) => Promise<{ principalId: string }>; | ||
| disconnect: () => Promise<void>; | ||
| getPrincipal: () => Promise<{ toText: () => string }>; | ||
| }; | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Plug wallet ICP requestConnect API return type documentation
💡 Result:
The Plug API method requestConnect returns a Promise — i.e., an async call that resolves to an object containing the connected account's publicKey (and throws if the user declines). [1]
Sources:
- Plug docs — "requestConnect" (returns Promise and returns an object containing the publicKey). [1]
Fix incorrect requestConnect return type in Plug wallet interface declaration
The requestConnect method is declared to return Promise<{ principalId: string }>, but according to the official Plug wallet API documentation, it returns Promise<PublicKey> containing a publicKey field, not principalId. Update the global interface declaration to match the actual Plug API:
requestConnect: (opts?: unknown) => Promise<{ publicKey: string }>;
🤖 Prompt for AI Agents
In packages/icp/src/wallets/plug.tsx around lines 1 to 16, the global
Window.ic.plug.requestConnect return type is incorrect (currently Promise<{
principalId: string }>) — update its type to match the Plug API by changing the
return shape to Promise<{ publicKey: string }>, keeping opts?: unknown and other
method signatures unchanged so TypeScript reflects the actual Plug wallet
response.
| useEffect(() => { | ||
| if (autoConnect) { | ||
| void (async () => { | ||
| await handleConnect(); | ||
| onAutoConnectConsumed(); | ||
| })(); | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [autoConnect, walletType]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useEffect 依赖项处理存在潜在问题
当前使用 eslint-disable-next-line react-hooks/exhaustive-deps 跳过了依赖检查。handleConnect 依赖于 installed、connect、messageApi 和 walletName,这些值在 walletType 变化时可能已经过期。
建议将 handleConnect 提取为 useCallback 并正确声明依赖,或者在 effect 内部直接调用 connect 逻辑。
+ const handleConnectRef = useRef(handleConnect);
+ handleConnectRef.current = handleConnect;
+
useEffect(() => {
if (autoConnect) {
void (async () => {
- await handleConnect();
+ await handleConnectRef.current();
onAutoConnectConsumed();
})();
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [autoConnect, walletType]);
+ }, [autoConnect, walletType, onAutoConnectConsumed]);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/web3/src/icp/demos/basic.tsx around lines 107 to 115, the useEffect
currently disables exhaustive-deps and calls handleConnect which closes over
installed, connect, messageApi and walletName — these values can become stale
when walletType changes. Fix by extracting handleConnect into a useCallback that
lists its true dependencies (installed, connect, messageApi, walletName, etc.)
and then reference that callback in the effect dependency array (or
alternatively move the connect logic inline inside the effect and include the
same dependencies), and remove the eslint-disable comment so the effect
dependencies are correct.
🚨 @wss-mz-web3, 本次 PR 的更改包含 major 变更,请确认是否符合预期。🚨 @wss-mz-web3, this PR contains major changes, please confirm if it is as expected.+'@ant-design/web3-icp': major
|
确认 |
|
|
||
| ## 介绍 | ||
|
|
||
| `@ant-design/web3-icp` 提供了对 ICP 链的基础适配能力,目前内置支持 **Plug** 钱包,后续可以按需扩展更多 ICP 钱包。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| `@ant-design/web3-icp` 提供了对 ICP 链的基础适配能力,目前内置支持 **Plug** 钱包,后续可以按需扩展更多 ICP 钱包。 | |
| `@ant-design/web3-icp` 提供了对 [ICP 链](https://dashboard.internetcomputer.org/)的基础适配能力,目前内置支持 **Plug** 钱包,后续可以按需扩展更多 ICP 钱包。 |
| }; | ||
|
|
||
| return ( | ||
| <IcpWeb3ConfigProvider walletType={walletType}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
plug 是一个钱包名吧?你应该是支持一个 wallets 的数组,方便以后扩展。类似 https://github.com/ant-design/ant-design-web3/blob/main/packages/solana/src/wallets/built-in.ts
plug 钱包的信息也可以维护在 https://github.com/ant-design/ant-design-web3/tree/main/packages/assets/src/wallets 里面。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感谢建议,相关修改已完成,请再 review 一下。
|
|
||
| return ( | ||
| <IcpWeb3ConfigProvider walletType={walletType}> | ||
| <IcpConnectButton |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
实现的方式不对,按照 antd web3 的设计,你应该是通过 https://web3.ant.design/components/web3-config-provider-cn 提供链接所需要的 context,然后引入 Connect 组件来使用,可以参考一下其它链的实现。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感谢建议,修改已完成,请再 review 一下
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (4)
packages/icp/src/icp-provider/config-provider.tsx (2)
8-12:ConnectAsync接口的resolve类型定义可以更精确
resolve参数定义为account?: Account,但 Promise 的泛型是Promise<Account>,这意味着 resolve 时应该总是传入一个 Account。建议将类型改为非可选参数以保持一致性。interface ConnectAsync { promise: Promise<Account>; - resolve: (account?: Account) => void; + resolve: (account: Account) => void; reject: (reason: any) => void; }
76-95:chainList中的filter检查可简化当前
.filter((item) => item !== null)是冗余的,因为map回调始终返回item(合并后的对象或原始对象),不会返回null。return availableChains .map((item) => { const c = chainAssets?.find((asset) => { return asset.id === item.id; }) as Chain; if (c?.id) { return { ...item, ...c, id: c.id, name: c.name, icon: c.icon, }; } return item; - }) - .filter((item) => item !== null) as (Chain & IcpChain)[]; + }) as (Chain & IcpChain)[];packages/icp/src/wallets/built-in.ts (1)
1-10: 缺少/* v8 ignore stop */结束注释。文件开头使用了
/* v8 ignore start */来忽略测试覆盖率,但缺少对应的结束注释。如果是有意忽略整个文件的覆盖率,建议添加注释说明原因。export const PlugWallet = (metadata?: Partial<WalletMetadata>): IcpWalletFactory => WalletFactory(createPlugWallet, { ...metadata_Plug, ...metadata }); +/* v8 ignore stop */packages/icp/src/icp-provider/index.tsx (1)
27-45: 钱包实例重复创建,可能导致状态不一致。
availableWallets和wallet两个useMemo都调用factory.create()创建新的钱包实例。如果create()返回的不是同一个对象引用,或者钱包状态有副作用,可能导致不一致的行为。建议先创建所有钱包实例,然后在两处复用:
+ const walletInstances = useMemo(() => { + return walletFactories.map((factory) => factory.create()); + }, [walletFactories]); + const availableWallets = useMemo(() => { - return walletFactories.map((factory) => { - const wallet = factory.create(); + return walletInstances.map((wallet) => { return { name: wallet.name, remark: wallet.name, icon: wallet.icon, hasExtensionInstalled: async () => wallet.installed, hasWalletReady: async () => wallet.installed, }; }); - }, [walletFactories]); + }, [walletInstances]); const wallet = useMemo(() => { // 优先选择已安装的钱包,如果没有已安装的,则选择第一个 - const walletInstances = walletFactories.map((factory) => factory.create()); const installedWallet = walletInstances.find((w) => w.installed); return installedWallet ?? walletInstances[0]; - }, [walletFactories]); + }, [walletInstances]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
packages/assets/src/icp/chains.tsx(1 hunks)packages/assets/src/icp/index.ts(1 hunks)packages/assets/src/index.ts(1 hunks)packages/assets/src/wallets/index.ts(1 hunks)packages/assets/src/wallets/plug.tsx(1 hunks)packages/common/src/types.ts(1 hunks)packages/icp/src/icp-provider/config-provider.tsx(1 hunks)packages/icp/src/icp-provider/index.tsx(1 hunks)packages/icp/src/index.ts(1 hunks)packages/icp/src/wallets/built-in.ts(1 hunks)packages/icp/src/wallets/factory.ts(1 hunks)packages/web3/src/icp/demos/basic.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/web3/src/icp/demos/basic.tsx
- packages/icp/src/index.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: This is a multi-package React project built on Ant Design component library, designed to provide Web3 projects with wallet connection capabilities and DApp UI components
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: This is a multi-package React project built on Ant Design component library, designed to provide Web3 projects with wallet connection capabilities and DApp UI components
Applied to files:
packages/icp/src/icp-provider/config-provider.tsxpackages/icp/src/icp-provider/index.tsx
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: UI components are located under `packages/web3`, with other packages containing adapters for different blockchains or foundational packages
Applied to files:
packages/assets/src/index.ts
🧬 Code graph analysis (5)
packages/icp/src/icp-provider/config-provider.tsx (3)
packages/common/src/types.ts (4)
Account(8-14)Locale(341-349)Chain(81-93)Wallet(141-149)packages/assets/src/icp/chains.tsx (1)
IcpChain(4-6)packages/icp/src/wallets/types.ts (1)
IcpWallet(3-12)
packages/icp/src/wallets/built-in.ts (4)
packages/common/src/types.ts (1)
WalletMetadata(186-255)packages/icp/src/wallets/factory.ts (2)
IcpWalletFactory(5-7)WalletFactory(14-25)packages/icp/src/wallets/plug.tsx (1)
createPlugWallet(23-73)packages/assets/src/wallets/plug.tsx (1)
metadata_Plug(4-20)
packages/assets/src/wallets/plug.tsx (2)
packages/common/src/types.ts (1)
WalletMetadata(186-255)packages/icons/src/components/chrome-circle-colorful.tsx (1)
ChromeCircleColorful(13-25)
packages/assets/src/icp/chains.tsx (2)
packages/common/src/types.ts (1)
Chain(81-93)packages/common/src/utils/createGetBrowserLink.ts (1)
createGetBrowserLink(1-9)
packages/icp/src/wallets/factory.ts (2)
packages/icp/src/wallets/types.ts (1)
IcpWallet(3-12)packages/common/src/types.ts (1)
WalletMetadata(186-255)
🔇 Additional comments (9)
packages/icp/src/icp-provider/config-provider.tsx (3)
51-61: 账户状态同步逻辑正确基于
principal正确更新账户状态,处理了连接和断开两种情况。
63-74: 连接状态处理逻辑合理正确处理了异步连接流程,在连接进行中时避免过早 resolve,并在完成后清理 ref。
125-145: 连接回调实现正确Promise 模式正确实现了异步连接流程,错误处理妥当。
_wallet和options参数未使用是因为 ICP 钱包连接不需要这些参数,但保留它们以符合Web3ConfigProvider的接口要求。packages/assets/src/index.ts (1)
4-4: 导出 ICP 模块符合现有模式新增的 ICP 模块导出与现有的
wallets、chains/ethereum、tokens导出保持一致,正确扩展了 assets 包的公共 API。packages/assets/src/wallets/index.ts (1)
18-18: Plug 钱包导出符合现有模式新增的 Plug 钱包导出与其他钱包(MetaMask、Phantom 等)的导出方式保持一致。
packages/common/src/types.ts (1)
47-49:IcpChainIds枚举定义正确新增的 ICP 链 ID 枚举遵循现有的
SolanaChainIds和SuiChainIds模式,结构清晰。packages/assets/src/icp/index.ts (1)
1-1: ICP 资产模块入口文件结构正确使用标准的 barrel export 模式聚合 ICP 相关的链定义,符合项目约定。
packages/icp/src/wallets/factory.ts (1)
1-25: LGTM! 工厂模式实现简洁清晰。类型定义完整,
WalletFactory正确地将基础钱包与元数据合并,使用空值合并运算符处理图标回退逻辑。packages/assets/src/wallets/plug.tsx (1)
4-20: Code structure looks good. Verify the Chrome extension link is accessible.The wallet metadata is properly formatted with all required fields. The
icon: nullis acceptable if the actual icon is provided elsewhere in the implementation. Consider verifying that the Chrome Web Store URL (https://chromewebstore.google.com/detail/plug/cfbfdhimifdmdehjmkdobpcjfefblkjm) is correct and publicly accessible.
| browser: { | ||
| icon: <IcpColorful />, | ||
| getBrowserLink: createGetBrowserLink('https://dashboard.internetcomputer.org'), | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ICP Dashboard 浏览器链接格式可能不正确。
createGetBrowserLink 工具函数生成的 URL 格式为 /address/{address} 和 /tx/{hash},但 ICP Dashboard 使用的是 /account/{principal} 和 /transaction/{txId} 格式。这会导致生成的浏览器链接无法正确跳转。
建议自定义 getBrowserLink 函数以匹配 ICP Dashboard 的 URL 结构:
browser: {
icon: <IcpColorful />,
- getBrowserLink: createGetBrowserLink('https://dashboard.internetcomputer.org'),
+ getBrowserLink: (address: string, type: string) => {
+ if (type === 'address') {
+ return `https://dashboard.internetcomputer.org/account/${address}`;
+ }
+ if (type === 'transaction') {
+ return `https://dashboard.internetcomputer.org/transaction/${address}`;
+ }
+ throw new Error(`getBrowserLink unsupported type ${type}`);
+ },
},Committable suggestion skipped: line range outside the PR's diff.
|
|
||
| const connectAsyncRef = useRef<ConnectAsync>(); | ||
| const [account, setAccount] = useState<Account>(); | ||
| const [balanceData, setBalanceData] = useState<bigint>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
balanceData 状态未被更新,余额功能不完整
balanceData 状态被定义但 setBalanceData 从未被调用,导致传递给 Web3ConfigProvider 的 balance.value 始终为 undefined。如果 balance 功能是预期的,需要添加获取和更新余额的逻辑。
是否需要我帮助实现余额获取逻辑?或者如果这是后续迭代的功能,建议添加 TODO 注释说明。
🤖 Prompt for AI Agents
In packages/icp/src/icp-provider/config-provider.tsx around line 49, the
balanceData state is defined but never updated; add logic to fetch the account
balance and call setBalanceData so Web3ConfigProvider receives a real value.
Specifically, add a useEffect that runs when the active wallet/account or
provider changes, call the appropriate ICP actor/provider method to get the
balance, convert the returned value to bigint if needed, and invoke
setBalanceData with that result; handle errors and loading cases and clean up
any subscriptions. If balance is intentionally out of scope for now, add a TODO
comment at this location stating that balance fetching will be implemented in a
follow-up and leave balanceData undefined until then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/icp/src/icp-provider/config-provider.tsx (1)
48-49:balanceData从未更新,balance打开时实际不会有余额数据
balanceData只在 Line 49 初始化,从未调用setBalanceData,但在balance为true时会被直接传入Web3ConfigProvider的balance.value,结果始终是undefined,余额能力名义存在但没有实际数据。建议:
- 如果本 PR 就要提供余额能力:在
principal/currentChainData变化时拉取 ICP 余额并更新balanceData;- 如果余额不在本次范围内:暂时移除
balance相关 props 或在这里加清晰的 TODO 注释,避免外部以为已经支持余额展示。Also applies to: 108-117
🧹 Nitpick comments (3)
packages/icp/src/icp-provider/config-provider.tsx (1)
32-45:wallet/_wallet未被使用,当前实现等同于“固定单钱包”,多钱包场景需要明确在该组件中:
props里解构了wallet,但后续完全未使用;connect签名为connect={async (_wallet, options) => {...}},实现中忽略了_wallet和options。这意味着:
- 现在无论 UI 选择哪个钱包,底层都走同一套
onConnect/onDisconnect流程;- 如果未来上层
IcpWeb3ConfigProvider支持多个钱包工厂,UI 展示的“可选钱包”与实际连接的钱包会不对齐。建议:
- 如果 ICP 目前只支持单一钱包(例如 Plug),可以在 props / 文档中明确“仅单钱包”,并移除未使用的
wallet/_wallet参数,简化接口;- 如果预期支持多钱包,则应利用
_wallet(或者传入的 id)在内部选择对应的钱包实例,或者在这里留下 TODO 注释,避免后续使用者误解。Also applies to: 121-148
packages/icp/src/icp-provider/index.tsx (2)
27-45: 重复调用factory.create()生成多份实例,可考虑统一实例化以减少混淆目前每个
IcpWalletFactory会在两个useMemo中各调用一次create():
availableWallets:用于生成展示信息;wallet:再次调用create()并基于installed选出一个实例。结果是:
- 同一个工厂会产生两套不同的
IcpWallet实例;- 虽然现在只在
connect/disconnect中使用wallet,不会出大问题,但后续如果需要基于同一个实例上的状态(例如缓存连接、事件监听)就会比较混乱。推荐简单重构为共用实例数组,例如:
const walletInstances = useMemo( () => walletFactories.map((factory) => factory.create()), [walletFactories], ); const availableWallets = useMemo( () => walletInstances.map((wallet) => ({ name: wallet.name, remark: wallet.name, icon: wallet.icon, hasExtensionInstalled: async () => wallet.installed, hasWalletReady: async () => wallet.installed, })), [walletInstances], ); const wallet = useMemo(() => { const installedWallet = walletInstances.find((w) => w.installed); return installedWallet ?? walletInstances[0]; }, [walletInstances]);可读性和行为都会更清晰一些。
23-25:currentChain不会随chainsProp 更新,动态切换链列表时可能出现不同步
currentChain初始值来源于chains[0]:const [currentChain, setCurrentChain] = useState<IcpChain | undefined>(chains[0]);但后续如果调用方在运行期改变了
chains(例如从 [Mainnet] 切成 [Testnet]),这里没有任何useEffect去同步currentChain,可能会出现:
chains已经换成新的列表;currentChain仍指向旧对象或旧 id,导致传给下层的链上下文不一致。如果预期
chains在整个生命周期内基本不变可以接受当前实现;否则建议在chains变化时进行一次校正,例如:
- 若当前
currentChain不在新的chains中,则回退到chains[0]或undefined。Also applies to: 75-75
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/icp/src/icp-provider/config-provider.tsx(1 hunks)packages/icp/src/icp-provider/index.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: This is a multi-package React project built on Ant Design component library, designed to provide Web3 projects with wallet connection capabilities and DApp UI components
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: UI components are located under `packages/web3`, with other packages containing adapters for different blockchains or foundational packages
📚 Learning: 2025-11-25T09:58:18.233Z
Learnt from: CR
Repo: ant-design/ant-design-web3 PR: 0
File: .cursor/rules/basic.mdc:0-0
Timestamp: 2025-11-25T09:58:18.233Z
Learning: This is a multi-package React project built on Ant Design component library, designed to provide Web3 projects with wallet connection capabilities and DApp UI components
Applied to files:
packages/icp/src/icp-provider/config-provider.tsxpackages/icp/src/icp-provider/index.tsx
🧬 Code graph analysis (2)
packages/icp/src/icp-provider/config-provider.tsx (3)
packages/common/src/types.ts (4)
Account(8-14)Locale(341-349)Chain(81-93)Wallet(141-149)packages/assets/src/icp/chains.tsx (1)
IcpChain(4-6)packages/icp/src/wallets/types.ts (1)
IcpWallet(3-12)
packages/icp/src/icp-provider/index.tsx (5)
packages/common/src/types.ts (1)
Locale(341-349)packages/assets/src/icp/chains.tsx (2)
IcpChain(4-6)Icp(8-17)packages/icp/src/wallets/factory.ts (1)
IcpWalletFactory(5-7)packages/icp/src/wallets/built-in.ts (1)
PlugWallet(9-10)packages/icp/src/icp-provider/config-provider.tsx (1)
AntDesignWeb3ConfigProvider(29-156)
🔇 Additional comments (1)
packages/icp/src/icp-provider/index.tsx (1)
47-60: Verify the promise resolution behavior of AntDesignWeb3ConfigProvider whengetPrincipal()returns nullThe review identifies a potential issue where
getPrincipal()can returnnull(perIcpWallettype), but the currentconnectimplementation doesn't handle this case. The concern is that ifnullis returned,setPrincipal(null)executes normally while the downstreamAntDesignWeb3ConfigProviderpromise may not resolve or reject properly, leaving callers hanging.The suggested fix—throwing an error when
getPrincipal()returns null—is logically sound and aligns with patterns in ICP development where null principal should be treated as a connection failure. However, this verification requires inspecting the actual promise handling logic inAntDesignWeb3ConfigProviderto confirm the hanging scenario described.
| const connectAsyncRef = useRef<ConnectAsync>(); | ||
| const [account, setAccount] = useState<Account>(); | ||
| const [balanceData, setBalanceData] = useState<bigint>(); | ||
|
|
||
| // get account address | ||
| useEffect(() => { | ||
| if (!principal) { | ||
| setAccount(undefined); | ||
| return; | ||
| } | ||
|
|
||
| setAccount({ | ||
| address: principal, | ||
| }); | ||
| }, [principal]); | ||
|
|
||
| // connect/disconnect wallet | ||
| useEffect(() => { | ||
| if (connecting && connectAsyncRef.current) { | ||
| // connecting in progress | ||
| return; | ||
| } | ||
|
|
||
| if (principal && connectAsyncRef.current) { | ||
| connectAsyncRef.current.resolve({ address: principal }); | ||
| connectAsyncRef.current = undefined; | ||
| } | ||
| }, [principal, connecting]); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find packages/icp/src -name "config-provider.tsx" -o -name "index.tsx" | head -20Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
fd -e tsx "config-provider" packages/icp/src/Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
fd -e tsx "index" packages/icp/src/icp-provider/Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
ls -la packages/icp/src/icp-provider/Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
wc -l packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
cat -n packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '120,160p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '40,80p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
cat -n packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "wallet\." packages/icp/src/icp-provider/index.tsx -A 3Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "interface.*Wallet|type.*Wallet" packages/icp/src/ -A 5Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "getPrincipal" packages/icp/src/ -B 2 -A 5Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '1,50p' packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '50,100p' packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "type IcpWeb3Config|interface IcpWeb3Config" packages/icp/src/ -A 10Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
find packages/icp/src -name "*.ts" -o -name "*.tsx" | xargs rg -l "interface.*Wallet"Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '100,145p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '90,130p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
grep -n "onConnect" packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '80,110p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '1,50p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
grep -n "connectAsyncRef" packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '40,50p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "connectAsyncRef" packages/icp/src/icp-provider/config-provider.tsx -B 2 -A 2Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "type ConnectAsync|interface ConnectAsync" packages/icp/src/Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "ConnectAsync" packages/icp/src/ -B 1 -A 3Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '125,150p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '1,30p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
fd -e ts -e tsx packages/icp/src/ | xargs grep -l "ConnectAsync"Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg "ConnectAsync" packages/icp/src/icp-provider/ -B 5 -A 5Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '127,145p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '1,80p' packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
wc -l packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
grep -n "principal" packages/icp/src/icp-provider/config-provider.tsx | head -20Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '50,75p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '45,90p' packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
grep -n "wallet.connect" packages/icp/src/icp-provider/index.tsx -B 5 -A 5Repository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '50,95p' packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
rg -n "timeout|setTimeout|catch|error" packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '1,120p' packages/icp/src/icp-provider/index.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '125,150p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '55,75p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
🏁 Script executed:
sed -n '125,155p' packages/icp/src/icp-provider/config-provider.tsxRepository: ant-design/ant-design-web3
Length of output: 196
Fix Promise hanging when wallet returns null principal without throwing
The connect() function creates a Promise that stores its resolve/reject in connectAsyncRef and waits for resolution from the useEffect([principal, connecting]). However, this effect only calls resolve when both principal && connectAsyncRef.current are truthy.
If wallet.getPrincipal() in the parent component's onConnect() returns null without throwing an error, the subsequent setPrincipal(null) causes the effect condition to fail—the Promise hangs indefinitely with no error feedback.
Add validation in onConnect():
const principal = await wallet.getPrincipal();
if (!principal) {
throw new Error('Failed to get principal from wallet');
}
setPrincipal(principal);This ensures the .catch() block in the connect() function is triggered, allowing reject to be called on the stored Promise so the caller receives proper error feedback instead of an indefinite hang.
🤖 Prompt for AI Agents
In packages/icp/src/icp-provider/config-provider.tsx around lines 47 to 75, the
connect() Promise can hang when the wallet returns a null principal because the
useEffect only resolves when principal is truthy; modify the onConnect() logic
where wallet.getPrincipal() is called to validate the result and throw an Error
if it is null/undefined (e.g., if (!principal) throw new Error('Failed to get
principal from wallet')), so the connect() caller's .catch() runs and the stored
Promise can be rejected instead of left hanging.
| const wallet = useMemo(() => { | ||
| // 优先选择已安装的钱包,如果没有已安装的,则选择第一个 | ||
| const walletInstances = walletFactories.map((factory) => factory.create()); | ||
| const installedWallet = walletInstances.find((w) => w.installed); | ||
| return installedWallet ?? walletInstances[0]; | ||
| }, [walletFactories]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
walletFactories 允许为空时,disconnect 会在运行时抛异常
wallet 的计算逻辑:
const walletInstances = walletFactories.map((factory) => factory.create());
const installedWallet = walletInstances.find((w) => w.installed);
return installedWallet ?? walletInstances[0];当 walletFactories 为空数组时,wallet 为 undefined。目前:
connect已经在 Line 47–50 做了if (!wallet) { return; }防御;- 但
disconnect中直接执行await wallet.disconnect();,会在wallet为undefined时抛出运行时错误。
建议加上与 connect 一致的防御逻辑,例如:
const disconnect = async () => {
- await wallet.disconnect();
- setPrincipal(null);
+ if (!wallet) {
+ setPrincipal(null);
+ return;
+ }
+ await wallet.disconnect();
+ setPrincipal(null);
};这样即使调用方传入 wallets={[]} 也不会导致崩溃。
Also applies to: 62-65
🤖 Prompt for AI Agents
packages/icp/src/icp-provider/index.tsx around lines 40-45 (and also apply same
fix at lines 62-65): the computed wallet can be undefined when walletFactories
is an empty array, so add the same defensive null-check used in connect to
disconnect; before calling await wallet.disconnect(); add if (!wallet) { return;
} (or guard early) so disconnect no longer throws when wallet is undefined—apply
the identical guard wherever wallet is used without a prior null check.

[中文版模板 / Chinese template]
💡 Background and solution
ICP 生态的主流钱包 Plug 尚未在示例或文档中提供明确链接,用户在使用 Web3 连接组件时容易找不到插件来源。
本 PR 增加了 Plug Wallet 的官方链接,方便开发者和用户快速获取 ICP 浏览器钱包,提高文档的可用性。
🔗 Related issue link
N/A
Summary by CodeRabbit
发布说明
新功能
文档
其他
✏️ Tip: You can customize this high-level summary in your review settings.