Skip to content
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

feat: Payment In USDT #1272

Merged
merged 8 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kind-eels-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ant-design/web3': minor
---

feat: Add chainSelect argument
106 changes: 106 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,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<PayButtonsProps> = ({
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 (
<div>
<WagmiWeb3ConfigProvider
config={config}
eip6963={{
autoAddInjectedWallets: true,
}}
wallets={[MetaMask()]}
chains={[mainnet, polygon, arbitrum, optimism]}
>
<Space size="middle">
<Connector
modalProps={{
footer: (
<>
Powered by{' '}
<a href="https://web3.ant.design/" target="_blank" rel="noreferrer">
Ant Design Web3
</a>
</>
),
}}
>
<ConnectButton chainSelect={false} />
Likang0122 marked this conversation as resolved.
Show resolved Hide resolved
</Connector>
<Connector
modalProps={{
footer: (
<>
Powered by{' '}
<a href="https://web3.ant.design/" target="_blank" rel="noreferrer">
Ant Design Web3
</a>
</>
),
}}
>
<ConnectButton chainSelect={false} />
</Connector>
<EvmSignTransaction
setTokenEcosystem={setTokenEcosystem}
tokenEcosystem={tokenEcosystem}
signTransaction={(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>
)}
/>
</Space>
</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
Likang0122 marked this conversation as resolved.
Show resolved Hide resolved
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="middle">
<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;
Likang0122 marked this conversation as resolved.
Show resolved Hide resolved
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<SignTransactionProps> = ({
setTokenEcosystem,
tokenEcosystem,
signTransaction,
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);
signTransaction?.(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',
},
],
};
46 changes: 46 additions & 0 deletions docs/guide/demos/best-practice/usdt.tsx
Original file line number Diff line number Diff line change
@@ -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<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">
<Space direction="vertical" size="middle">
<Title level={3}>Select Chain</Title>
<ChainSelect ecosystem={tokenEcosystem} onChange={setTokenEcosystem} />
<EvmPayButton
setTokenEcosystem={setTokenEcosystem}
tokenEcosystem={tokenEcosystem}
signCallback={onSubmitCashier}
payCallback={runPay}
onRejectSwitchChain={(id) => {
const oldTokenEcosystem = TOKEN_PAY_ADDRESS.chains.find(
(item) => item.id === id,
)?.ecosystem;
setTokenEcosystem(oldTokenEcosystem || '');
}}
/>
</Space>
</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

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:

<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>
Loading
Loading