Skip to content

Commit

Permalink
feat: Payment In USDT
Browse files Browse the repository at this point in the history
  • Loading branch information
Likang0122 committed Dec 3, 2024
1 parent e0db15a commit 0e8dbd8
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 1 deletion.
92 changes: 92 additions & 0 deletions docs/guide/demos/best-practice/components/pay-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// /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, Flex } 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;
};

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',
}),
],
});

export const EvmPayButtons: React.FC<PayButtonsProps> = ({
setTokenEcosystem,
tokenEcosystem,
signCallback,
payCallback,
onRejectSwitchChain,
}) => {
return (
<div>
<WagmiWeb3ConfigProvider
config={config}
eip6963={{
autoAddInjectedWallets: true,
}}
wallets={[MetaMask()]}
chains={[mainnet, polygon, arbitrum, optimism]}
>
<Flex justify="space-between" style={{ width: 400, marginTop: 20 }}>
<Connector
modalProps={{
footer: (
<>
Powered by{' '}
<a href="https://web3.ant.design/" target="_blank" rel="noreferrer">
Ant Design Web3
</a>
</>
),
}}
>
<ConnectButton showChainSelect={false} />
</Connector>
<EvmSignTransaction
setTokenEcosystem={setTokenEcosystem}
tokenEcosystem={tokenEcosystem}
signTransactionCallback={(signTransfer, address) => {
payCallback(signTransfer, address);
}}
onRejectSwitchChain={onRejectSwitchChain}
renderSignButton={(signTransfer, disabled, signLoading) => (
<Button
type="primary"
style={{ width: 200 }}
loading={signLoading}
disabled={disabled}
onClick={() => {
signCallback(signTransfer);
}}
>
Pay
</Button>
)}
/>
</Flex>
</WagmiWeb3ConfigProvider>
</div>
);
};
35 changes: 35 additions & 0 deletions docs/guide/demos/best-practice/components/select.tsx
Original file line number Diff line number Diff line change
@@ -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<ChainSelectGroupProps> = (props) => {
const chainList = TOKEN_PAY_ADDRESS.chains;

return (
<Space size={16}>
<Radio.Group
onChange={(e) => {
props.onChange(e.target.value);
}}
value={props.ecosystem}
>
{chainList.map((info) => {
return (
<Radio key={info.ecosystem} value={info.ecosystem}>
{info.name}
</Radio>
);
})}
</Radio.Group>
</Space>
);
};

export default ChainSelect;
106 changes: 106 additions & 0 deletions docs/guide/demos/best-practice/components/send.tsx
Original file line number Diff line number Diff line change
@@ -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;
signTransactionCallback?: (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<SignTransactionProps> = ({
setTokenEcosystem,
tokenEcosystem,
signTransactionCallback,
renderSignButton,
onRejectSwitchChain,
}) => {
const [signLoading, setSignLoading] = useState<boolean>(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);
signTransactionCallback?.(signTransferHash, account?.address || '');
} catch (error) {
console.log('error', (error as any).message);
setSignLoading(false);
}
};

return <div>{renderSignButton(signTransfer, !account, signLoading)}</div>;
};
export default EvmSignTransaction;
58 changes: 58 additions & 0 deletions docs/guide/demos/best-practice/constants/tokenPayAddress.ts
Original file line number Diff line number Diff line change
@@ -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',
},
],
};
44 changes: 44 additions & 0 deletions docs/guide/demos/best-practice/usdt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// /usdt.tsx
import React, { useState } from 'react';
import { Button, Card, Flex, message, Radio, Space, Typography } from 'antd';

import { EvmPayButtons } 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<string>('');

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 (
<Card title="Payment in USDT">
<Title level={3}>Select Chain</Title>
<ChainSelect ecosystem={tokenEcosystem} onChange={setTokenEcosystem} />
<EvmPayButtons
setTokenEcosystem={setTokenEcosystem}
tokenEcosystem={tokenEcosystem}
signCallback={onSubmitCashier}
payCallback={runPay}
onRejectSwitchChain={(id) => {
const oldTokenEcosystem = TOKEN_PAY_ADDRESS.chains.find(
(item) => item.id === id,
)?.ecosystem;
setTokenEcosystem(oldTokenEcosystem || '');
}}
/>
</Card>
);
};

export default PaymentInUSDT;
14 changes: 14 additions & 0 deletions docs/guide/payment-in-usdt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
nav: Payment In USDT
group: Best Practice
---

# Payment In USDT

当你的项目需要支持 `USDT` 付款的时候,并且需要支持多个链的 `USDT` / `USDC` 时,下边这些可以帮到你。

你可以通过我们官方提供的适配器配合 `@ant-design/web3` 使用,快速连接各类区块链,以便于同时支持这些链的 `USDT` / `USDC` 支付。

你可以这样做:

<code compact src="./demos/best-practice/usdt.tsx"></code>
14 changes: 14 additions & 0 deletions docs/guide/payment-in-usdt.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
nav: 用USDT支付
group: 最佳实践
---

# 用USDT支付

当你的项目需要支持 `USDT` 付款的时候,并且需要支持多个链的 `USDT` / `USDC` 时,下边这些可以帮到你。

你可以通过我们官方提供的适配器配合 `@ant-design/web3` 使用,快速连接各类区块链,以便于同时支持这些链的 `USDT` / `USDC` 支付。

你可以这样做:

<code compact src="./demos/best-practice/usdt.tsx"></code>
5 changes: 4 additions & 1 deletion packages/web3/src/connect-button/connect-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ConnectButtonTooltip } from './tooltip';

export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
const {
showChainSelect = true,
onConnectClick,
onDisconnectClick,
availableChains,
Expand Down Expand Up @@ -125,7 +126,9 @@ export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
};

const chainSelect =
availableChains && availableChains.length > 1 ? <ChainSelect {...chainProps} /> : null;
showChainSelect && availableChains && availableChains.length > 1 ? (
<ChainSelect {...chainProps} />
) : null;

const buttonInnerText = (
<div className={`${prefixCls}-content`}>
Expand Down
1 change: 1 addition & 0 deletions packages/web3/src/connect-button/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ConnectButtonTooltipProps = TooltipProps & {

export type ConnectButtonProps = ButtonProps &
ConnectorTriggerProps & {
showChainSelect?: boolean | true;
prefixCls?: string;
locale?: Locale['ConnectButton'];
avatar?: AvatarProps;
Expand Down

0 comments on commit 0e8dbd8

Please sign in to comment.