Skip to content

Commit

Permalink
feat(web3js): support web3js adapter by wagmi solution (#772)
Browse files Browse the repository at this point in the history
* feat(web3js): add web3js sub pkg

* example(web3js): add eth-web3js example

* chore(web3js): finish web3js example

* docs(web3js): add doc

* chore(web3js): add changesets

* test(web3js): fix coverage
  • Loading branch information
jeasonstudio committed May 15, 2024
1 parent 02e74aa commit 17c450f
Show file tree
Hide file tree
Showing 32 changed files with 974 additions and 41 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-birds-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ant-design/web3": patch
---

chore(web3js): add doc and demo for web3js
5 changes: 5 additions & 0 deletions .changeset/silver-seahorses-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ant-design/web3-eth-web3js": major
---

feat(web3js): support web3js adapter for ethereum
11 changes: 11 additions & 0 deletions examples/eth-web3js/.umirc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from 'umi';

export default defineConfig({
routes: [{ path: '/', component: 'index' }],
npmClient: 'pnpm',
chainWebpack(config) {
// See: https://github.com/webpack/webpack/issues/11467
config.module.rule('mjs-rule').resolve.set('fullySpecified', false);
},
mfsu: false,
});
26 changes: 26 additions & 0 deletions examples/eth-web3js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@example/eth-web3js",
"private": true,
"author": "Jeason <me@cowpoke.cc>",
"type": "module",
"scripts": {
"dev": "umi dev",
"build": "umi build",
"postinstall": "umi setup",
"setup": "umi setup"
},
"dependencies": {
"@ant-design/web3": "workspace:*",
"@ant-design/web3-assets": "workspace:*",
"@ant-design/web3-common": "workspace:*",
"@ant-design/web3-eth-web3js": "workspace:*",
"antd": "^5.15.4",
"web3": "^4.7.0",
"umi": "^4.1.5"
},
"devDependencies": {
"@types/react": "^18.2.73",
"@types/react-dom": "^18.2.22",
"typescript": "^5.4.3"
}
}
53 changes: 53 additions & 0 deletions examples/eth-web3js/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ConnectButton, Connector } from '@ant-design/web3';
import { Mainnet } from '@ant-design/web3-assets';
import {
EthWeb3jsConfigProvider,
MetaMask,
OkxWallet,
TokenPocket,
useWeb3js,
} from '@ant-design/web3-eth-web3js';
import { Button, message } from 'antd';

const App = () => {
const [messageApi, contextHolder] = message.useMessage();

const web3 = useWeb3js();

return (
<div>
{contextHolder}
<Connector modalProps={{ mode: 'simple' }}>
<ConnectButton quickConnect style={{ minWidth: 120 }} />
</Connector>
<Button
onClick={async () => {
const [address] = await web3!.eth.getAccounts();
if (!address || !web3) {
messageApi.error('Please connect wallet first!');
return;
}

const signature = await web3.eth.personal.sign(web3.utils.utf8ToHex('hi'), address, '');
const blockNum = await web3.eth.getBlockNumber();
messageApi.success(`Sig: ${signature}, current block number: ${blockNum}`);
}}
>
Sign Message
</Button>
</div>
);
};

export default function HomePage() {
return (
<EthWeb3jsConfigProvider
ens
eip6963={{ autoAddInjectedWallets: true }}
wallets={[MetaMask(), TokenPocket(), OkxWallet()]}
chains={[Mainnet]}
>
<App />
</EthWeb3jsConfigProvider>
);
}
3 changes: 3 additions & 0 deletions examples/eth-web3js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./src/.umi/tsconfig.json"
}
1 change: 1 addition & 0 deletions examples/eth-web3js/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'umi/typings';
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"build:docs": "dumi build",
"example:ethers": "pnpm run --filter \"@example/ethers\" dev",
"example:ethers-v5": "pnpm run --filter \"@example/ethers-v5\" dev",
"example:eth-web3js": "pnpm run --filter \"@example/eth-web3js\" dev",
"postinstall": "pnpm run husky:prepare",
"changeset": "changeset",
"release:prepare": "npm run build && npm run lint && npm run test:ci",
Expand Down Expand Up @@ -114,5 +115,10 @@
"dependencies": {
"@ant-design/cssinjs": "1.20.0",
"cross-env": "^7.0.3"
},
"pnpm": {
"overrides": {
"wagmi": "^2.5.7"
}
}
}
5 changes: 5 additions & 0 deletions packages/eth-web3js/.fatherrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from 'father';

export default defineConfig({
extends: '../../.fatherrc.base.ts',
});
3 changes: 3 additions & 0 deletions packages/eth-web3js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @ant-design/web3-eth-web3js

[https://web3.ant.design](https://web3.ant.design)
77 changes: 77 additions & 0 deletions packages/eth-web3js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "@ant-design/web3-eth-web3js",
"version": "0.1.0",
"type": "module",
"main": "dist/esm/index.js",
"module": "dist/esm/index.js",
"typings": "dist/esm/index.d.ts",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/lib/index.js",
"types": "./dist/esm/index.d.ts"
},
"./wagmi": {
"import": "./dist/esm/wagmi.js",
"require": "./dist/lib/wagmi.js",
"types": "./dist/esm/wagmi.d.ts"
}
},
"files": [
"dist",
"CHANGELOG.md",
"README.md"
],
"keywords": [
"ant",
"component",
"components",
"design",
"framework",
"frontend",
"react",
"react-component",
"ui",
"web3js"
],
"homepage": "https://we3.ant.design",
"bugs": {
"url": "https://github.com/ant-design/ant-design-web3/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/ant-design/ant-design-web3"
},
"scripts": {
"dev": "father dev",
"build": "father build"
},
"dependencies": {
"@ant-design/web3-assets": "workspace:*",
"@ant-design/web3-common": "workspace:*",
"@ant-design/web3-wagmi": "workspace:*",
"@tanstack/react-query": "^5.28.4",
"debug": "^4.3.4",
"viem": ">=2.0.0",
"wagmi": "^2.5.7"
},
"devDependencies": {
"@types/debug": "^4.1.12",
"father": "^4.3.8",
"typescript": "^5.4.2",
"web3": "^4.8.0"
},
"peerDependencies": {
"web3": "^4.0.0"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"browserslist": [
"last 2 versions",
"Firefox ESR",
"> 1%",
"ie >= 11"
]
}
5 changes: 5 additions & 0 deletions packages/eth-web3js/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { EIP1193Provider } from 'viem';

declare interface Window {
ethereum?: EIP1193Provider;
}
1 change: 1 addition & 0 deletions packages/eth-web3js/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './use-web3js';
27 changes: 27 additions & 0 deletions packages/eth-web3js/src/hooks/use-web3js.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Mainnet } from '@ant-design/web3-assets';
import { render } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';

import { MetaMask } from '../wallets';
import { EthWeb3jsConfigProvider } from '../web3js-provider';
import { useWeb3js } from './use-web3js';

describe('useWeb3js', async () => {
test('basic usage', async () => {
const CustomConnector: React.FC = () => {
useWeb3js();
return <div className="text">web3js</div>;
};

const App = () => (
<EthWeb3jsConfigProvider wallets={[MetaMask()]} chains={[Mainnet]}>
<CustomConnector />
</EthWeb3jsConfigProvider>
);

const { baseElement } = render(<App />);
await vi.waitFor(() => {
expect(baseElement.querySelector('.text')?.textContent).toBe('web3js');
});
});
});
17 changes: 17 additions & 0 deletions packages/eth-web3js/src/hooks/use-web3js.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useMemo } from 'react';
import type { Chain, Client, Transport } from 'viem';
import { useConnectorClient, type Config } from 'wagmi';
import { Web3 } from 'web3';

export const clientToWeb3js = (client: Client<Transport, Chain>): Web3 => {
const { transport } = client;
if (transport.type === 'fallback') {
return new Web3(transport.transports[0].value.url);
}
return new Web3(transport);
};

export function useWeb3js(): Web3 | null {
const { data: client } = useConnectorClient<Config>();
return useMemo(() => (client ? clientToWeb3js(client) : null), [client]);
}
3 changes: 3 additions & 0 deletions packages/eth-web3js/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './web3js-provider';
export * from './wallets';
export * from './hooks';
4 changes: 4 additions & 0 deletions packages/eth-web3js/src/wagmi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from 'wagmi';
export * as actions from 'wagmi/actions';
export * as chains from 'wagmi/chains';
export * as connectors from 'wagmi/connectors';
9 changes: 9 additions & 0 deletions packages/eth-web3js/src/wallets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export {
MetaMask,
CoinbaseWallet,
TokenPocket,
SafeheronWallet,
UniversalWallet,
OkxWallet,
ImToken,
} from '@ant-design/web3-wagmi';
1 change: 1 addition & 0 deletions packages/eth-web3js/src/web3js-provider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './web3js-provider';
105 changes: 105 additions & 0 deletions packages/eth-web3js/src/web3js-provider/web3js-provider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import { useProvider } from '@ant-design/web3';
import { Mainnet, Optimism } from '@ant-design/web3-assets';
import { render } from '@testing-library/react';
import { describe, expect, test } from 'vitest';

import { MetaMask, TokenPocket } from '../wallets';
import { EthWeb3jsConfigProvider } from './web3js-provider';

describe('web3js-provider', async () => {
test('basic usage', async () => {
const CustomConnector: React.FC = () => {
const { availableChains } = useProvider();
return (
<div className="chains-name">{availableChains?.map((item) => item.name).join(',')}</div>
);
};

const App = () => (
<EthWeb3jsConfigProvider>
<CustomConnector />
</EthWeb3jsConfigProvider>
);

const { baseElement } = render(<App />);
expect(baseElement.querySelector('.chains-name')?.textContent).toBe('Ethereum');
});

test('chains', async () => {
const CustomConnector: React.FC = () => {
const { availableChains } = useProvider();
return (
<div className="chains-name">{availableChains?.map((item) => item.name).join(',')}</div>
);
};

const App = () => (
<EthWeb3jsConfigProvider chains={[Mainnet, Optimism]}>
<CustomConnector />
</EthWeb3jsConfigProvider>
);

const { baseElement } = render(<App />);
expect(baseElement.querySelector('.chains-name')?.textContent).toBe('Ethereum,OP Mainnet');
});

test('chains but not found', async () => {
const CustomConnector: React.FC = () => {
const { availableChains } = useProvider();
return (
<div className="chains-name">{availableChains?.map((item) => item.name).join(',')}</div>
);
};

const UnknownChain = { ...Mainnet, name: 'Unknown', id: -9999 };

const App = () => (
<EthWeb3jsConfigProvider chains={[Mainnet, UnknownChain]}>
<CustomConnector />
</EthWeb3jsConfigProvider>
);

const { baseElement } = render(<App />);
expect(baseElement.querySelector('.chains-name')?.textContent).toBe('Ethereum');
});

test('wallets', async () => {
const CustomConnector: React.FC = () => {
const { availableWallets } = useProvider();
return (
<div className="wallets-name">{availableWallets?.map((item) => item.name).join(',')}</div>
);
};

const App = () => (
<EthWeb3jsConfigProvider wallets={[MetaMask(), TokenPocket()]}>
<CustomConnector />
</EthWeb3jsConfigProvider>
);

const { baseElement } = render(<App />);
expect(baseElement.querySelector('.wallets-name')?.textContent).toBe('MetaMask,TokenPocket');
});

test('wallet-connect', async () => {
const CustomConnector: React.FC = () => {
const { availableWallets } = useProvider();
return (
<div className="wallets-name">{availableWallets?.map((item) => item.name).join(',')}</div>
);
};

const App = () => (
<EthWeb3jsConfigProvider
wallets={[MetaMask()]}
walletConnect={{ projectId: 'YOUR_WALLET_CONNECT_PROJECT_ID' }}
>
<CustomConnector />
</EthWeb3jsConfigProvider>
);

const { baseElement } = render(<App />);
expect(baseElement.querySelector('.wallets-name')?.textContent).toBe('MetaMask,WalletConnect');
});
});

0 comments on commit 17c450f

Please sign in to comment.