Skip to content

Commit

Permalink
feat: init @ant-design/web3-wagmi (#83)
Browse files Browse the repository at this point in the history
* feat: init @ant-design/web3-wagmi

* feat: init WagmiWeb3ConfigProvider demo and doc

* chore: update module type

* fix: rename eslintrc.js to eslintrc.cjs for fix test error

* chore: config jsMinifierOptions for fix dumi build

* feat: refact wallets in @ant-design/web3-ethereum and support accounts and wallets in @ant-design/web3-wagmi

* feat: init getNFTMetadata in wagmi provider

* fix: use bigint for tokenId for fix ci

* feat: support showQrCode with WalletConnect

* fix: use bigint for fix ci

* fix: update tsconfig for fix eslint ci bug

* test: add a simple test case for WagmiWeb3ConfigProvider

* feat: NFTImage support use number as tokenId

* feat: wrap WagmiConfig in WagmiWeb3ConfigProvider

* fix: metamask install judge

* choreL move wagmi/core dep to devDep

* docs: update WagmiWeb3ConfigProvider usage

---------

Co-authored-by: yutingzhao1991 <[email protected]>
  • Loading branch information
yutingzhao1991 and yutingzhao1991 authored Nov 7, 2023
1 parent db5211b commit b802b5d
Show file tree
Hide file tree
Showing 75 changed files with 1,670 additions and 442 deletions.
10 changes: 0 additions & 10 deletions .dumi/tsconfig.json

This file was deleted.

4 changes: 4 additions & 0 deletions .dumirc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ export default defineConfig({
},
],
},
jsMinifierOptions: {
target: ['chrome80', 'es2020'],
},
copy: ['CNAME'],
define: {
YOUR_ZAN_API_KEY: 'd0eeefc2a4da4a8ba707889259b437d6',
YOUR_INFURA_API_KEY: '287294cbc30b44efab9455664b69b130',
YOUR_WALLET_CONNET_PROJECT_ID: 'c07c0051c2055890eade3556618e38a6',
},
publicPath: process.env.PUBLIC_PATH || '/',
Expand Down
6 changes: 3 additions & 3 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ dist
lib
es

# 只检查 ts 文件
**/*.js
vitest.config.mts
.umi-production
.umi-test
.umi
11 changes: 11 additions & 0 deletions docs/guide/wagmi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
nav: Guide
group: Advance
---

# Using with wagmi

<!-- prettier-ignore -->
:::warning
Waiting for translation
:::
38 changes: 38 additions & 0 deletions docs/guide/wagmi.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
nav: 指南
group: 高级
---

# 和 wagmi 一起使用

你可以通过我们提供的 `@ant-design/web3-wagmi` 简单便捷地和 [wagmi](https://wagmi.sh/) 一起使用。wagmi 是一个面向以太坊的 React Hooks 库,它不提供 UI,Ant Design Web3 可以作为它的一个很好的补充。

另外通过 `@ant-design/web3-wagmi`,Ant Design Web3 的组件可以基于 wagmi 提供的 Hooks 更加便捷可靠地连接到区块链,使用的示例如下:

```tsx | pure
import { WagmiConfig, createConfig, configureChains, mainnet } from 'wagmi';
import { publicProvider } from 'wagmi/providers/public';
import { WagmiWeb3ConfigProvider } from '@ant-design/web3-wagmi';
import { NFTImage } from '@ant-design/web3';

const { chains, publicClient } = configureChains([mainnet], [publicProvider()]);

const config = createConfig({
autoConnect: true,
publicClient,
});

function App() {
return (
<WagmiWeb3ConfigProvider config={config}>
<NFTImage address="0x79fcdef22feed20eddacbb2587640e45491b757f" tokenId={42} />
</WagmiWeb3ConfigProvider>
);
}
```

除了需要引入 `WagmiWeb3ConfigProvider` 替代 `WagmiConfig` 外,你完全不需要改变 wagmi 的任何用法。

## 使用示例

<code src="../../packages/web3/src/connect-button/demos/wagmi.tsx"></code>
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"test:coverage": "vitest --coverage",
"test:ci": "vitest --coverage",
"lint": "pnpm run \"/^lint:.+/\"",
"lint:eslint": "eslint --quiet --fix --ext .ts,.js .",
"lint:eslint": "eslint --ext .ts,.tsx .",
"lint:prettier": "prettier --loglevel warn --write '**/*.{ts,tsx,js,jsx,json,md}'",
"lint:ts": "tsc --noEmit",
"clean": "pnpm run \"/^clean:.+/\"",
Expand All @@ -39,17 +39,20 @@
"*.{json,less,md}": "prettier --ignore-unknown --write"
},
"devDependencies": {
"@ant-design/web3": "workspace:*",
"@ant-design/web3-ethereum": "workspace:*",
"@ant-design/web3-wagmi": "workspace:*",
"@biomejs/biome": "^1.0.0",
"@changesets/changelog-git": "^0.1.14",
"@changesets/cli": "^2.26.2",
"@testing-library/react": "^14.0.0",
"@biomejs/biome": "^1.0.0",
"@types/node": "^16.6.1",
"@types/react": "^18.2.20",
"@umijs/fabric": "^2.14.1",
"@vitest/coverage-v8": "^0.34.6",
"antd": "^5.8.3",
"dumi": "^2.2.6",
"eslint": "^7.32.0",
"eslint": "^8.53.0",
"ethers": "^6.7.1",
"father": "^4.1.9",
"husky": "^8.0.3",
Expand All @@ -60,7 +63,8 @@
"react-dom": "^18.2.0",
"rimraf": "^5.0.0",
"typescript": "^5.0.4",
"vitest": "^0.34.6"
"vitest": "^0.34.6",
"wagmi": "^1.4.5"
},
"ci": {
"type": "aci",
Expand Down
6 changes: 1 addition & 5 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,5 @@
"Firefox ESR",
"> 1%",
"ie >= 11"
],
"dependencies": {
"@walletconnect/ethereum-provider": "^2.10.2",
"@walletconnect/modal": "^2.6.2"
}
]
}
3 changes: 1 addition & 2 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export * from './types';
export * from './wallets/index';
export * from './rpc-providers';
export * from './utils';
export * from './web3-config-provider';
export * from './wallets';

export * as chains from './chains';
41 changes: 2 additions & 39 deletions packages/common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,54 +40,17 @@ export interface UniversalWeb3ProviderInterface {
requestAccounts?: (wallet?: string) => Promise<Account[]>;
disconnect?: () => Promise<void>;

getAccounts?: () => Promise<Account[]>;
getCurrentAccount?: () => Promise<Account | undefined>;
getCurrentNetwork?: () => Promise<number>;
getNFTMetadata?: (params: { address: string; tokenId: number }) => Promise<NFTMetadata>;
getAvaliableWallets?: () => Promise<Wallet[]>;
getNFTMetadata?: (params: { address: string; tokenId: bigint }) => Promise<NFTMetadata>;
}

/**
* This interface is a subset of the EIP-1193 provider interface.
* See: https://eips.ethereum.org/EIPS/eip-1193
*/
export interface EIP1193LikeProvider {
request: (request: { method: string; params?: any }) => Promise<any>;
// connect and disconnect for WallectConnect
connect?: () => Promise<void>;
disconnect?: () => Promise<void>;
networkVersion?: string;
}

export interface WalletProviderOptions {
chains?: Chain[];
}

export interface Wallet extends WalletMetadata, EIP1193IncludeProvider {
provider?: EIP1193LikeProvider;
export interface Wallet extends WalletMetadata {
hasBrowserExtensionInstalled?: () => Promise<boolean>;
getQrCode?: () => Promise<{
uri: string;
}>;
}

export interface EIP1193IncludeProvider {
provider?: EIP1193LikeProvider;
}

export interface EIP1193IncludeProviderFactory {
create: (options?: WalletProviderOptions) => Promise<EIP1193IncludeProvider>;
}

export interface WalletProvider extends EIP1193IncludeProviderFactory {
metadata: WalletMetadata;
create: (options?: WalletProviderOptions) => Promise<Wallet>;
}

export interface JsonRpcProvider extends EIP1193IncludeProviderFactory {
getRpcUrl: (chain: Chain) => string;
}

/**
* @desc 浏览器扩展程序信息
*/
Expand Down
14 changes: 14 additions & 0 deletions packages/common/src/utils/format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, it, expect } from 'vitest';
import { fillAddressWith0x, parseNumberToBigint } from './format';

describe('utils.format', () => {
it('should fill address with 0x', () => {
expect(fillAddressWith0x('0x123')).toBe('0x123');
expect(fillAddressWith0x('123')).toBe('0x123');
});

it('should parse number to bigint', () => {
expect(parseNumberToBigint(123)).toBe(123n);
expect(parseNumberToBigint(123n)).toBe(123n);
});
});
7 changes: 7 additions & 0 deletions packages/common/src/utils/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function fillAddressWith0x(address: string): `0x${string}` {
return (address.startsWith('0x') ? address : `0x${address}`) as `0x${string}`;
}

export function parseNumberToBigint(num: number | bigint) {
return typeof num !== 'bigint' ? BigInt(num) : num;
}
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './request';
export * from './format';
52 changes: 18 additions & 34 deletions packages/common/src/wallets/meta-mask.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,20 @@
import { WalletMetadata, Wallet, WalletProvider } from '../types';
import { WalletMetadata } from '../types';

export class MetaMaskProvider implements WalletProvider {
metadata: WalletMetadata = {
icon: 'https://metamask.io/images/metamask-logo.png',
name: 'MetaMask',
remark: 'MetaMask Wallet',
app: {
link: 'https://metamask.io/',
export const metadata_MetaMask: WalletMetadata = {
icon: 'https://metamask.io/images/metamask-logo.png',
name: 'MetaMask',
remark: 'MetaMask Wallet',
app: {
link: 'https://metamask.io/',
},
extensions: [
{
key: 'Chrome',
browserIcon:
'https://github.com/ant-design/ant-design/assets/10286961/0d4e4ac7-8f89-4147-a06a-de72c02e85cb',
browserName: 'Chrome',
link: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
description: 'Access your wallet right from your favorite web browser.',
},
extensions: [
{
key: 'Chrome',
browserIcon:
'https://github.com/ant-design/ant-design/assets/10286961/0d4e4ac7-8f89-4147-a06a-de72c02e85cb',
browserName: 'Chrome',
link: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
description: 'Access your wallet right from your favorite web browser.',
},
],
};

create = async (): Promise<Wallet> => {
if (!window.ethereum) {
throw new Error('MetaMask is not installed');
}
return {
provider: window.ethereum,
...this.metadata,
};
};

hasBrowserExtensionInstalled = async (): Promise<boolean> => {
return window.ethereum && window.ethereum?.isMetaMask ? true : false;
};
}
],
};
91 changes: 11 additions & 80 deletions packages/common/src/wallets/wallet-connect.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,11 @@
// Only for WallectConnect v2, v1 is not supported
import { Wallet, WalletMetadata, WalletProvider, WalletProviderOptions } from '../types';
import { EthereumProvider } from '@walletconnect/ethereum-provider';

export interface WalletConnectConfig {
projectId: string;
showQrModal?: boolean;
}

export class WalletConnectProvider implements WalletProvider {
metadata: WalletMetadata = {
icon: 'https://docs.walletconnect.com/img/walletconnect-logo-black.svg#light-mode-only',
name: 'WalletConnect',
remark: 'Connect with mobile APP',
app: {
link: 'https://walletconnect.com/',
},
group: 'Popular',
};

private qrCodeLinkPromise: Promise<string> | undefined;
private resolveQrCodeLink: ((uri: string) => void) | undefined;

constructor(private config?: WalletConnectConfig) {}

create = async (options?: WalletProviderOptions): Promise<Wallet> => {
const { projectId, showQrModal = false } = this.config || {};
if (!projectId) {
throw new Error('walletConnectProjectId is required');
}
let chains = [1];
if (options?.chains && options?.chains?.length > 0) {
chains = options.chains.map((chain) => chain.id as number);
}
// docs: https://docs.walletconnect.com/advanced/providers/ethereum
const provider = await EthereumProvider.init({
projectId: projectId,
chains: [chains[0]],
optionalChains: chains,
showQrModal,
methods: ['eth_requestAccounts', 'eth_accounts'],
events: [],
});

provider.on('display_uri', (uri: string) => {
this.resolveQrCodeLink?.(uri);
});

provider.on('disconnect', () => {
this.initQrCodePromise();
});

this.initQrCodePromise();

return {
provider,
...this.metadata,
getQrCode: async () => {
if (!this.qrCodeLinkPromise) {
throw new Error('WalletConnect is not initialized');
}
const uri = await this.qrCodeLinkPromise;
return {
uri,
};
},
};
};

hasBrowserExtensionInstalled = async (): Promise<boolean> => {
// If showQrModal is true, it means use WalletConnet offcial modal, Think it's the same as installing extension
return this.config?.showQrModal ?? false;
};

private initQrCodePromise = () => {
this.qrCodeLinkPromise = new Promise((resolve) => {
this.resolveQrCodeLink = resolve;
});
};
}
import { WalletMetadata } from '../types';

export const metadata_WalletConnect: WalletMetadata = {
icon: 'https://docs.walletconnect.com/img/walletconnect-logo-black.svg#light-mode-only',
name: 'WalletConnect',
remark: 'Connect with mobile APP',
app: {
link: 'https://walletconnect.com/',
},
group: 'Popular',
};
5 changes: 1 addition & 4 deletions packages/common/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "."
},
"include": ["src", "global.d.ts"]
"include": ["src", "global.d.ts", "../ethereum/src/wallets"]
}
4 changes: 3 additions & 1 deletion packages/ethereum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"dependencies": {
"@ant-design/web3-common": "workspace:*",
"debug": "^4.3.4",
"eventemitter3": "^5.0.1"
"eventemitter3": "^5.0.1",
"@walletconnect/ethereum-provider": "^2.10.2",
"@walletconnect/modal": "^2.6.2"
},
"devDependencies": {
"@types/debug": "^4.1.9",
Expand Down
Loading

0 comments on commit b802b5d

Please sign in to comment.