From 2ee466ca3f9d337ef560b5de5a08a8968457d870 Mon Sep 17 00:00:00 2001 From: LiKang Date: Tue, 24 Dec 2024 11:30:12 +0800 Subject: [PATCH] feat: Payment In USDT (#1272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Payment In USDT * fix: add translate * fix: Flex to Space * fix: EvmPayButtons -> EvmPayButton * fix: Add chainSelect argument * Update .changeset/kind-eels-press.md * Update docs/guide/payment-in-usdt.zh-CN.md Co-authored-by: thinkasany <480968828@qq.com> * Update docs/guide/payment-in-usdt.zh-CN.md Co-authored-by: thinkasany <480968828@qq.com> --------- Co-authored-by: 愚指导 Co-authored-by: thinkasany <480968828@qq.com> --- .changeset/kind-eels-press.md | 5 + .../best-practice/components/pay-button.tsx | 106 ++++++++++++++++++ .../demos/best-practice/components/select.tsx | 35 ++++++ .../demos/best-practice/components/send.tsx | 106 ++++++++++++++++++ .../constants/tokenPayAddress.ts | 58 ++++++++++ docs/guide/demos/best-practice/usdt.tsx | 46 ++++++++ docs/guide/payment-in-usdt.md | 14 +++ docs/guide/payment-in-usdt.zh-CN.md | 14 +++ .../connect-button/__tests__/chains.test.tsx | 14 +++ .../src/connect-button/connect-button.tsx | 9 +- packages/web3/src/connect-button/index.md | 1 + .../web3/src/connect-button/index.zh-CN.md | 1 + packages/web3/src/connect-button/interface.ts | 1 + 13 files changed, 407 insertions(+), 3 deletions(-) create mode 100644 .changeset/kind-eels-press.md create mode 100644 docs/guide/demos/best-practice/components/pay-button.tsx create mode 100644 docs/guide/demos/best-practice/components/select.tsx create mode 100644 docs/guide/demos/best-practice/components/send.tsx create mode 100644 docs/guide/demos/best-practice/constants/tokenPayAddress.ts create mode 100644 docs/guide/demos/best-practice/usdt.tsx create mode 100644 docs/guide/payment-in-usdt.md create mode 100644 docs/guide/payment-in-usdt.zh-CN.md diff --git a/.changeset/kind-eels-press.md b/.changeset/kind-eels-press.md new file mode 100644 index 000000000..e459796c1 --- /dev/null +++ b/.changeset/kind-eels-press.md @@ -0,0 +1,5 @@ +--- +'@ant-design/web3': minor +--- + +feat: Add chainSelect argument diff --git a/docs/guide/demos/best-practice/components/pay-button.tsx b/docs/guide/demos/best-practice/components/pay-button.tsx new file mode 100644 index 000000000..8cc3780d0 --- /dev/null +++ b/docs/guide/demos/best-practice/components/pay-button.tsx @@ -0,0 +1,106 @@ +// /components/pay-button.tsx +import React from 'react'; +import { ConnectButton, Connector } from '@ant-design/web3'; +import { MetaMask, WagmiWeb3ConfigProvider } from '@ant-design/web3-wagmi'; +import { Button, Space } from 'antd'; +import { createConfig, http } from 'wagmi'; +import { arbitrum, mainnet, optimism, polygon } from 'wagmi/chains'; +import { injected } from 'wagmi/connectors'; + +import EvmSignTransaction from './send'; + +type PayButtonsProps = { + setTokenEcosystem?: (token: string) => void; + tokenEcosystem: string; + signCallback: (signTransfer: (toAddress: string, amount: number) => void) => void; + payCallback: (signTransfer: string, address: string) => void; + onRejectSwitchChain?: (id: number) => void; +}; + +export const EvmPayButton: React.FC = ({ + setTokenEcosystem, + tokenEcosystem, + signCallback, + payCallback, + onRejectSwitchChain, +}) => { + const config = createConfig({ + chains: [mainnet, polygon, arbitrum, optimism], + transports: { + [mainnet.id]: http(), + [polygon.id]: http(), + [arbitrum.id]: http(), + [optimism.id]: http(), + }, + connectors: [ + injected({ + target: 'metaMask', + }), + ], + }); + + return ( +
+ + + + Powered by{' '} + + Ant Design Web3 + + + ), + }} + > + + + + Powered by{' '} + + Ant Design Web3 + + + ), + }} + > + + + { + payCallback(signTransfer, address); + }} + onRejectSwitchChain={onRejectSwitchChain} + renderSignButton={(signTransfer, disabled, signLoading) => ( + + )} + /> + + +
+ ); +}; diff --git a/docs/guide/demos/best-practice/components/select.tsx b/docs/guide/demos/best-practice/components/select.tsx new file mode 100644 index 000000000..a3ce7c35f --- /dev/null +++ b/docs/guide/demos/best-practice/components/select.tsx @@ -0,0 +1,35 @@ +// /components/select.tsx +import React from 'react'; +import { Radio, Space } from 'antd'; + +import { TOKEN_PAY_ADDRESS } from '../constants/tokenPayAddress'; + +interface ChainSelectGroupProps { + ecosystem: string; + onChange: (ecosystem: string) => void; +} + +const ChainSelect: React.FC = (props) => { + const chainList = TOKEN_PAY_ADDRESS.chains; + + return ( + + { + props.onChange(e.target.value); + }} + value={props.ecosystem} + > + {chainList.map((info) => { + return ( + + {info.name} + + ); + })} + + + ); +}; + +export default ChainSelect; diff --git a/docs/guide/demos/best-practice/components/send.tsx b/docs/guide/demos/best-practice/components/send.tsx new file mode 100644 index 000000000..b8f87dda1 --- /dev/null +++ b/docs/guide/demos/best-practice/components/send.tsx @@ -0,0 +1,106 @@ +// /components/send.tsx +import React, { useEffect, useState } from 'react'; +import { useAccount } from '@ant-design/web3'; +import { parseAbi, parseUnits } from 'viem'; +import { useChainId, useSwitchChain, useWriteContract } from 'wagmi'; + +import { TOKEN_PAY_ADDRESS } from '../constants/tokenPayAddress'; + +type SignTransactionProps = { + setTokenEcosystem?: (token: string) => void; + tokenEcosystem: string; + signTransaction: (signTransfer: string, address: string) => void; + renderSignButton: ( + signTransfer: (toAddress: string, amount: number) => void, + disabled: boolean, + loading: boolean, + ) => React.ReactNode; + onRejectSwitchChain?: (id: number) => void; +}; + +const EvmSignTransaction: React.FC = ({ + setTokenEcosystem, + tokenEcosystem, + signTransaction, + renderSignButton, + onRejectSwitchChain, +}) => { + const [signLoading, setSignLoading] = useState(false); + const { writeContractAsync } = useWriteContract(); + const { switchChain } = useSwitchChain(); + const chainId = useChainId(); + const { account } = useAccount(); + + useEffect(() => { + if (account?.address) { + const chainList = TOKEN_PAY_ADDRESS.chains; + const changeChainId = chainList.find((item) => item.ecosystem === tokenEcosystem)?.id; + if (changeChainId && changeChainId !== chainId) { + switchChain?.( + { chainId: changeChainId }, + { + onError: (error) => { + if (error.message.includes('User rejected')) { + onRejectSwitchChain?.(chainId); + } + }, + }, + ); + } + } + }, [tokenEcosystem, account]); + + useEffect(() => { + if (chainId && !tokenEcosystem) { + const chainList = TOKEN_PAY_ADDRESS.chains; + const initTokenEcosystem = chainList.find((item) => item.id === chainId)?.ecosystem; + if (initTokenEcosystem && account) { + setTokenEcosystem?.(initTokenEcosystem); + } else { + setTokenEcosystem?.(chainList[0].ecosystem); + } + } + }, [account]); + + const signTransfer = async (toAddress: string, amount: number) => { + try { + setSignLoading(true); + // transfer ABI + // { + // "constant": false, + // "inputs": [ + // { "name": "_to", "type": "address" }, + // { "name": "_value", "type": "uint256" } + // ], + // "name": "transfer", + // "outputs": [], + // "payable": false, + // "stateMutability": "nonpayable", + // "type": "function" + // }, + const decimals = TOKEN_PAY_ADDRESS.chains.find( + (item) => item.ecosystem === tokenEcosystem, + )?.decimals; + const contractAddress = TOKEN_PAY_ADDRESS.chains.find( + (item) => item.ecosystem === tokenEcosystem, + )?.address; + const signTransferHash = await writeContractAsync({ + abi: parseAbi(['function transfer(address _to, uint256 _value)']), + address: contractAddress as `0x${string}`, + functionName: 'transfer', + args: [ + toAddress.toLocaleLowerCase() as `0x${string}`, + parseUnits(amount.toString(), decimals!), + ], + }); + setSignLoading(false); + signTransaction?.(signTransferHash, account?.address || ''); + } catch (error) { + console.log('error', (error as any).message); + setSignLoading(false); + } + }; + + return
{renderSignButton(signTransfer, !account, signLoading)}
; +}; +export default EvmSignTransaction; diff --git a/docs/guide/demos/best-practice/constants/tokenPayAddress.ts b/docs/guide/demos/best-practice/constants/tokenPayAddress.ts new file mode 100644 index 000000000..ee5aef5ad --- /dev/null +++ b/docs/guide/demos/best-practice/constants/tokenPayAddress.ts @@ -0,0 +1,58 @@ +// /constants/tokenPayAddress.ts +interface TokenInfo { + name: string; + icon: string; + symbol: string; + chains: { + name: string; + id?: number; + decimals: number; + ecosystem: string; + address: string; + txScan?: string; + network?: string; + }[]; +} +export const TOKEN_PAY_ADDRESS: TokenInfo = { + name: 'USDT', + icon: 'https://mdn.alipayobjects.com/huamei_hsbbrh/afts/img/A*HkpaQoYlReEAAAAAAAAAAAAADiOMAQ/original', + symbol: 'usdt', + chains: [ + { + name: 'Ethereum', + id: 1, + decimals: 6, + ecosystem: 'ethereum', + network: 'mainnet', + txScan: 'https://etherscan.io/tx/', + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + }, + { + name: 'Polygon', + id: 137, + decimals: 6, + ecosystem: 'polygon', + network: 'polygon', + txScan: 'https://polygonscan.com/tx/', + address: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', + }, + { + name: 'Arbitrum', + id: 42161, + decimals: 6, + ecosystem: 'arbitrum', + network: 'arbitrum', + txScan: 'https://arbiscan.io/tx/', + address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', + }, + { + name: 'Optimism', + id: 10, + decimals: 6, + ecosystem: 'optimism', + network: 'optimism', + txScan: 'https://optimistic.etherscan.io/tx/', + address: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', + }, + ], +}; diff --git a/docs/guide/demos/best-practice/usdt.tsx b/docs/guide/demos/best-practice/usdt.tsx new file mode 100644 index 000000000..0809a2cae --- /dev/null +++ b/docs/guide/demos/best-practice/usdt.tsx @@ -0,0 +1,46 @@ +// /usdt.tsx +import React, { useState } from 'react'; +import { Card, message, Space, Typography } from 'antd'; + +import { EvmPayButton } from './components/pay-button'; +import ChainSelect from './components/select'; +import { TOKEN_PAY_ADDRESS } from './constants/tokenPayAddress'; + +const { Title } = Typography; + +const PaymentInUSDT: React.FC = () => { + // token ecosystem + const [tokenEcosystem, setTokenEcosystem] = useState(''); + + const onSubmitCashier = async (sign: (toAddress: string, amount: number) => void) => { + // The address and amount are obtained from the backend service + sign('0x35ceCD3d51Fe9E5AD14ea001475668C5A5e5ea76', 10); + }; + + const runPay = async (sign: string, address: string) => { + message.success('Pay success'); + }; + + return ( + + + Select Chain + + { + const oldTokenEcosystem = TOKEN_PAY_ADDRESS.chains.find( + (item) => item.id === id, + )?.ecosystem; + setTokenEcosystem(oldTokenEcosystem || ''); + }} + /> + + + ); +}; + +export default PaymentInUSDT; diff --git a/docs/guide/payment-in-usdt.md b/docs/guide/payment-in-usdt.md new file mode 100644 index 000000000..b87095700 --- /dev/null +++ b/docs/guide/payment-in-usdt.md @@ -0,0 +1,14 @@ +--- +nav: Payment In USDT +group: Best Practice +--- + +# Payment In USDT + +When your project needs to support `USDT` payments and needs to support `USDT/USDC` on multiple chains, the following can help you. + +You can use our official adapter with `@ant-design/web3` to quickly connect to various blockchains to support `USDT/USDC` payments on these chains at the same time. + +You can do this: + + diff --git a/docs/guide/payment-in-usdt.zh-CN.md b/docs/guide/payment-in-usdt.zh-CN.md new file mode 100644 index 000000000..5f078b07e --- /dev/null +++ b/docs/guide/payment-in-usdt.zh-CN.md @@ -0,0 +1,14 @@ +--- +nav: 用 USDT 支付 +group: 最佳实践 +--- + +# 用 USDT 支付 + +当你的项目需要支持 `USDT` 付款的时候,并且需要支持多个链的 `USDT` / `USDC` 时,下边这些可以帮到你。 + +你可以通过我们官方提供的适配器配合 `@ant-design/web3` 使用,快速连接各类区块链,以便于同时支持这些链的 `USDT` / `USDC` 支付。 + +你可以这样做: + + diff --git a/packages/web3/src/connect-button/__tests__/chains.test.tsx b/packages/web3/src/connect-button/__tests__/chains.test.tsx index 6cbae370f..ddc18e5f5 100644 --- a/packages/web3/src/connect-button/__tests__/chains.test.tsx +++ b/packages/web3/src/connect-button/__tests__/chains.test.tsx @@ -69,4 +69,18 @@ describe('ConnectButton chains', () => { ), ).toBeTruthy(); }); + + it('without chain select', async () => { + const App: React.FC = () => { + return ( + + ); + }; + const { baseElement } = render(); + expect(baseElement.querySelector('.ant-web3-connect-button-chain-select-button')).toBeNull(); + }); }); diff --git a/packages/web3/src/connect-button/connect-button.tsx b/packages/web3/src/connect-button/connect-button.tsx index 0e4c1bd81..85a3b9bd7 100644 --- a/packages/web3/src/connect-button/connect-button.tsx +++ b/packages/web3/src/connect-button/connect-button.tsx @@ -21,6 +21,7 @@ import { ConnectButtonTooltip } from './tooltip'; export const ConnectButton: React.FC = (props) => { const { + chainSelect = true, onConnectClick, onDisconnectClick, availableChains, @@ -124,8 +125,10 @@ export const ConnectButton: React.FC = (props) => { size: props.size, }; - const chainSelect = - availableChains && availableChains.length > 1 ? : null; + const chainSelectRender = + chainSelect && availableChains && availableChains.length > 1 ? ( + + ) : null; const buttonInnerText = (
@@ -147,7 +150,7 @@ export const ConnectButton: React.FC = (props) => { { diff --git a/packages/web3/src/connect-button/index.md b/packages/web3/src/connect-button/index.md index 3c774ece5..852cd99a1 100644 --- a/packages/web3/src/connect-button/index.md +++ b/packages/web3/src/connect-button/index.md @@ -72,6 +72,7 @@ After configuring the `quickConnect` property, the installed wallets and univers | availableWallets | List of available wallets | [Wallet](../types/index.md#wallet)\[] | - | - | | quickConnect | Quick connect | `boolean` | `false` | - | | locale | Multilingual settings | `Locale["ConnectButton"]` | - | - | +| chainSelect | Selection chain | `boolean` | `true` | - | ### Balance diff --git a/packages/web3/src/connect-button/index.zh-CN.md b/packages/web3/src/connect-button/index.zh-CN.md index 362c6a1b4..2245ccf13 100644 --- a/packages/web3/src/connect-button/index.zh-CN.md +++ b/packages/web3/src/connect-button/index.zh-CN.md @@ -73,6 +73,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_mutawc/afts/img/A*S4cyQ7OCxDUAAA | availableWallets | 可用的钱包列表 | [Wallet](../types/index.zh-CN.md#wallet)\[] | - | - | | quickConnect | 快速连接钱包 | `boolean` | `false` | - | | locale | 多语言设置 | `Locale["ConnectButton"]` | - | - | +| chainSelect | 是否显示选择链 | `boolean` | `true` | - | ### Balance diff --git a/packages/web3/src/connect-button/interface.ts b/packages/web3/src/connect-button/interface.ts index b7e1f18b6..a1540682d 100644 --- a/packages/web3/src/connect-button/interface.ts +++ b/packages/web3/src/connect-button/interface.ts @@ -15,6 +15,7 @@ export type ConnectButtonTooltipProps = TooltipProps & { export type ConnectButtonProps = ButtonProps & ConnectorTriggerProps & { + chainSelect?: boolean | true; prefixCls?: string; locale?: Locale['ConnectButton']; avatar?: AvatarProps;