name: multiversx-dapp-frontend description: 将 React 应用适配到 MultiversX 区块链,支持钱包连接、交易和智能合约交互。当应用需要 Web3、区块链、钱包登录、加密货币支付、NFT、代币或智能合约调用时使用。
MultiversX dApp 前端集成
将 React 应用转换为 MultiversX 区块链 dApp,实现钱包认证、交易签名和智能合约交互。
先决条件
起始项目已包含 MultiversX SDK 包:
@multiversx/sdk-core- 核心区块链原语@multiversx/sdk-dapp- React hooks 和 providers@multiversx/sdk-dapp-core-ui- 预构建 UI 组件@multiversx/sdk-dapp-utils- 工具函数
应用初始化
关键: 在渲染 React 应用之前调用 initApp()。
修改 src/main.tsx:
import { createRoot } from 'react-dom/client';
import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp';
import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types';
import App from './App';
import './index.css';
const config = {
storage: {
getStorageCallback: () => sessionStorage
},
dAppConfig: {
environment: EnvironmentsEnum.devnet, // 更改为 testnet 或 mainnet
nativeAuth: {
expirySeconds: 86400, // 24 小时
tokenExpirationToastWarningSeconds: 300 // 5 分钟警告
}
}
};
initApp(config).then(() => {
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
});
环境选项
| 环境 | 值 | 使用场景 |
|---|---|---|
EnvironmentsEnum.devnet |
开发 | 使用免费代币测试 |
EnvironmentsEnum.testnet |
测试 | 预生产测试 |
EnvironmentsEnum.mainnet |
生产 | 真实交易 |
钱包连接与 UnlockPanelManager
UnlockPanelManager 提供一个预构建 UI,支持所有钱包提供者。
设置解锁面板
import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager';
import { useNavigate } from 'react-router-dom';
function ConnectWalletButton() {
const navigate = useNavigate();
const handleConnect = () => {
const unlockPanelManager = UnlockPanelManager.init({
loginHandler: () => {
navigate('/dashboard'); // 登录成功后重定向
},
onClose: () => {
// 可选:处理未登录关闭面板
}
});
unlockPanelManager.openUnlockPanel();
};
return (
<button onClick={handleConnect}>
连接钱包
</button>
);
}
支持的钱包提供者
UnlockPanelManager 自动显示所有可用提供者:
- xPortal App - 通过二维码的移动钱包
- xPortal Hub - 基于浏览器的 xPortal
- MultiversX DeFi Wallet - 浏览器扩展
- Web Wallet - MultiversX 网页钱包
- Ledger - 硬件钱包
- WalletConnect - WalletConnect v2 协议
- Passkey - WebAuthn/FIDO2 认证
登出
import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/accountProvider';
async function handleLogout() {
const provider = getAccountProvider();
await provider.logout();
navigate('/'); // 重定向到首页
}
认证状态钩子
检查登录状态
import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/hooks/account/useGetIsLoggedIn';
function AuthStatus() {
const isLoggedIn = useGetIsLoggedIn();
return isLoggedIn ? <Dashboard /> : <LoginPrompt />;
}
获取账户数据
import { useGetAccount } from '@multiversx/sdk-dapp/out/hooks/account/useGetAccount';
function AccountInfo() {
const { address, balance, nonce } = useGetAccount();
return (
<div>
<p>地址: {address}</p>
<p>余额: {balance} EGLD</p>
</div>
);
}
仅获取地址
import { useGetAddress } from '@multiversx/sdk-dapp/out/hooks/account/useGetAddress';
function AddressDisplay() {
const address = useGetAddress();
return <span>{address}</span>;
}
获取网络配置
import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/hooks/useGetNetworkConfig';
function NetworkInfo() {
const { network } = useGetNetworkConfig();
return (
<div>
<p>链 ID: {network.chainId}</p>
<p>标签: {network.egldLabel}</p>
</div>
);
}
受保护路由
创建认证守卫组件用于保护页面:
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/hooks/account/useGetIsLoggedIn';
interface AuthGuardProps {
children: React.ReactNode;
}
function AuthGuard({ children }: AuthGuardProps) {
const isLoggedIn = useGetIsLoggedIn();
const navigate = useNavigate();
const [isChecking, setIsChecking] = useState(true);
useEffect(() => {
// 小延迟以允许 SDK 恢复会话
const timer = setTimeout(() => {
setIsChecking(false);
if (!isLoggedIn) {
navigate('/');
}
}, 100);
return () => clearTimeout(timer);
}, [isLoggedIn, navigate]);
if (isChecking) {
return <div>加载中...</div>;
}
return isLoggedIn ? <>{children}</> : null;
}
// 在路由中使用
<Route path="/dashboard" element={
<AuthGuard>
<Dashboard />
</AuthGuard>
} />
基本交易
发送 EGLD 转账
import { Transaction, Address } from '@multiversx/sdk-core';
import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/accountProvider';
import { refreshAccount } from '@multiversx/sdk-dapp/out/utils/account/refreshAccount';
import { TransactionManager } from '@multiversx/sdk-dapp/out/managers/TransactionManager';
import { useGetAccount } from '@multiversx/sdk-dapp/out/hooks/account/useGetAccount';
import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/hooks/useGetNetworkConfig';
function SendEgld() {
const { address: senderAddress, nonce } = useGetAccount();
const { network } = useGetNetworkConfig();
const sendTransaction = async (receiverAddress: string, amount: string) => {
// 刷新账户以获取最新 nonce
await refreshAccount();
// 创建交易(金额以 wei 为单位:1 EGLD = 10^18)
const transaction = new Transaction({
value: BigInt(amount),
receiver: Address.newFromBech32(receiverAddress),
sender: Address.newFromBech32(senderAddress),
gasLimit: BigInt(50000),
chainID: network.chainId,
nonce: BigInt(nonce)
});
// 签名交易
const provider = getAccountProvider();
const signedTransactions = await provider.signTransactions([transaction]);
// 发送和跟踪
const txManager = TransactionManager.getInstance();
const sentTransactions = await txManager.send(signedTransactions);
// 通过 toast 通知跟踪
const sessionId = await txManager.track(sentTransactions, {
transactionsDisplayInfo: {
processingMessage: '发送 EGLD 中...',
successMessage: 'EGLD 发送成功!',
errorMessage: '交易失败'
}
});
return sessionId;
};
return (
<button onClick={() => sendTransaction('erd1...', '1000000000000000000')}>
发送 1 EGLD
</button>
);
}
发送 ESDT 代币转账
import { Transaction, Address, TokenTransfer } from '@multiversx/sdk-core';
import { EsdtHelpers } from '@multiversx/sdk-dapp-utils';
async function sendToken(
receiverAddress: string,
tokenId: string,
amount: string,
decimals: number
) {
await refreshAccount();
const { address: senderAddress, nonce } = useGetAccount();
const { network } = useGetNetworkConfig();
// 构建代币转账数据
const transfer = TokenTransfer.fungibleFromAmount(tokenId, amount, decimals);
const transaction = new Transaction({
value: BigInt(0),
receiver: Address.newFromBech32(receiverAddress),
sender: Address.newFromBech32(senderAddress),
gasLimit: BigInt(500000),
chainID: network.chainId,
nonce: BigInt(nonce),
data: Buffer.from(`ESDTTransfer@${Buffer.from(tokenId).toString('hex')}@${transfer.amountAsBigInteger.toString(16)}`)
});
const provider = getAccountProvider();
const signedTransactions = await provider.signTransactions([transaction]);
const txManager = TransactionManager.getInstance();
await txManager.send(signedTransactions);
}
交易状态跟踪
监控待处理交易
import { useGetPendingTransactions } from '@multiversx/sdk-dapp/out/hooks/transactions/useGetPendingTransactions';
function PendingTransactions() {
const { pendingTransactions, hasPendingTransactions } = useGetPendingTransactions();
if (!hasPendingTransactions) {
return <p>没有待处理交易</p>;
}
return (
<ul>
{pendingTransactions.map((tx) => (
<li key={tx.hash}>处理中: {tx.hash}</li>
))}
</ul>
);
}
监控成功交易
import { useGetSuccessfulTransactions } from '@multiversx/sdk-dapp/out/hooks/transactions/useGetSuccessfulTransactions';
function SuccessfulTransactions() {
const { successfulTransactions } = useGetSuccessfulTransactions();
return (
<ul>
{successfulTransactions.map((tx) => (
<li key={tx.hash}>已完成: {tx.hash}</li>
))}
</ul>
);
}
监控失败交易
import { useGetFailedTransactions } from '@multiversx/sdk-dapp/out/hooks/transactions/useGetFailedTransactions';
function FailedTransactions() {
const { failedTransactions } = useGetFailedTransactions();
return (
<ul>
{failedTransactions.map((tx) => (
<li key={tx.hash}>失败: {tx.hash}</li>
))}
</ul>
);
}
高级智能合约交互
加载 ABI 文件
将 ABI JSON 文件存储在 src/contracts/ 并导入:
// src/contracts/myContract.abi.json - 在此放置您的 ABI 文件
import { AbiRegistry, SmartContract, Address } from '@multiversx/sdk-core';
import myContractAbi from '@/contracts/myContract.abi.json';
function createContract(contractAddress: string) {
const abiRegistry = AbiRegistry.create(myContractAbi);
return new SmartContract({
address: Address.newFromBech32(contractAddress),
abi: abiRegistry
});
}
查询智能合约(视图函数)
import { ApiNetworkProvider } from '@multiversx/sdk-core';
import { SmartContract, Address, AbiRegistry, ResultsParser } from '@multiversx/sdk-core';
async function queryContract() {
const networkProvider = new ApiNetworkProvider('https://devnet-api.multiversx.com');
const contract = new SmartContract({
address: Address.newFromBech32('erd1qqqqqqqqqqqqqq...'),
abi: AbiRegistry.create(myContractAbi)
});
// 为视图函数创建查询
const query = contract.createQuery({
func: 'getTokenPrice',
args: [] // 如果需要,添加参数
});
// 执行查询
const queryResponse = await networkProvider.queryContract(query);
// 使用 ABI 解析结果
const resultsParser = new ResultsParser();
const endpointDefinition = contract.getEndpoint('getTokenPrice');
const parsed = resultsParser.parseQueryResponse(queryResponse, endpointDefinition);
return parsed.values[0]; // 第一个返回值
}
调用智能合约(状态改变函数)
import { SmartContract, Address, AbiRegistry, TokenTransfer } from '@multiversx/sdk-core';
import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/accountProvider';
import { refreshAccount } from '@multiversx/sdk-dapp/out/utils/account/refreshAccount';
import { TransactionManager } from '@multiversx/sdk-dapp/out/managers/TransactionManager';
async function callContract(
contractAddress: string,
functionName: string,
args: any[],
egldValue?: string
) {
await refreshAccount();
const contract = new SmartContract({
address: Address.newFromBech32(contractAddress),
abi: AbiRegistry.create(myContractAbi)
});
// 构建交互
const interaction = contract.methods[functionName](args);
// 设置 gas 限制
interaction.withGasLimit(BigInt(10000000));
// 如果需要,添加 EGLD 支付
if (egldValue) {
interaction.withValue(BigInt(egldValue));
}
// 构建交易
const transaction = interaction.buildTransaction();
// 签名
const provider = getAccountProvider();
const signedTransactions = await provider.signTransactions([transaction]);
// 发送和跟踪
const txManager = TransactionManager.getInstance();
const sent = await txManager.send(signedTransactions);
await txManager.track(sent, {
transactionsDisplayInfo: {
processingMessage: `调用 ${functionName} 中...`,
successMessage: '交易成功!',
errorMessage: '交易失败'
}
});
}
批量/多调用交易
并行发送多个交易:
async function batchTransactions(transactions: Transaction[]) {
const provider = getAccountProvider();
const signedTransactions = await provider.signTransactions(transactions);
const txManager = TransactionManager.getInstance();
// 并行发送所有
const sent = await txManager.send(signedTransactions);
await txManager.track(sent, {
transactionsDisplayInfo: {
processingMessage: '处理批量交易中...',
successMessage: '所有交易完成!',
errorMessage: '部分交易失败'
}
});
}
顺序发送交易(等待每个完成):
async function sequentialTransactions(transactions: Transaction[]) {
const provider = getAccountProvider();
const signedTransactions = await provider.signTransactions(transactions);
const txManager = TransactionManager.getInstance();
// 使用嵌套数组顺序发送
const sent = await txManager.send(signedTransactions.map(tx => [tx]));
await txManager.track(sent);
}
智能合约与代币支付
async function callWithTokenPayment(
contractAddress: string,
functionName: string,
tokenId: string,
amount: string,
decimals: number
) {
const contract = new SmartContract({
address: Address.newFromBech32(contractAddress),
abi: AbiRegistry.create(myContractAbi)
});
const payment = TokenTransfer.fungibleFromAmount(tokenId, amount, decimals);
const interaction = contract.methods[functionName]([])
.withSingleESDTTransfer(payment)
.withGasLimit(BigInt(15000000));
const transaction = interaction.buildTransaction();
// 签名和发送...
}
UI 组件
格式化代币金额
import { MvxFormatAmount } from '@multiversx/sdk-dapp-core-ui';
function BalanceDisplay({ amount }: { amount: string }) {
return (
<MvxFormatAmount
value={amount}
decimals={18}
egldLabel="EGLD"
showLabel
/>
);
}
显示交易表
import { MvxTransactionsTable } from '@multiversx/sdk-dapp-core-ui';
function TransactionsPage() {
return (
<div>
<h2>您的交易</h2>
<MvxTransactionsTable />
</div>
);
}
关键知识
错误:硬编码链 ID
// 错误 - 在不同网络上会中断
const transaction = new Transaction({
chainID: 'D' // 硬编码 devnet
});
正确:使用网络配置
// 正确 - 动态链 ID
const { network } = useGetNetworkConfig();
const transaction = new Transaction({
chainID: network.chainId
});
错误:交易前未刷新账户
// 错误 - 可能使用过时的 nonce
const { nonce } = useGetAccount();
const tx = new Transaction({ nonce: BigInt(nonce) });
正确:始终先刷新账户
// 正确 - 新鲜 nonce
await refreshAccount();
const { nonce } = useGetAccount();
const tx = new Transaction({ nonce: BigInt(nonce) });
错误:gas 限制不足
// 错误 - 50,000 gas 仅适用于简单 EGLD 转账
const tx = new Transaction({
gasLimit: BigInt(50000),
data: Buffer.from('ESDTTransfer@...') // 代币转账需要更多 gas!
});
正确:适当的 gas 限制
// 按操作类型正确的 gas 限制:
// 简单 EGLD 转账:50,000
// ESDT 代币转账:500,000
// NFT 转账:1,000,000
// 智能合约调用:5,000,000 - 60,000,000(取决于复杂性)
const tx = new Transaction({
gasLimit: BigInt(500000) // 用于代币转账
});
错误:在 initApp 完成前渲染
// 错误 - SDK 未初始化
createRoot(document.getElementById('root')!).render(<App />);
initApp(config); // 太晚了!
正确:等待 initApp
// 正确 - 渲染前 SDK 已准备
initApp(config).then(() => {
createRoot(document.getElementById('root')!).render(<App />);
});
API 端点参考
| 网络 | API URL |
|---|---|
| 开发网 | https://devnet-api.multiversx.com |
| 测试网 | https://testnet-api.multiversx.com |
| 主网 | https://api.multiversx.com |
SDK 文档链接
- SDK-dApp 文档: https://github.com/multiversx/mx-sdk-dapp
- 模板 dApp(参考实现): https://github.com/multiversx/mx-template-dapp
- SDK-Core 文档: https://github.com/multiversx/mx-sdk-js-core
- MultiversX 文档: https://docs.multiversx.com
验证清单
完成前验证:
- [ ]
initApp()在 main.tsx 中调用,在createRoot().render()之前 - [ ] 环境设置正确(devnet/testnet/mainnet)
- [ ] 钱包连接与 UnlockPanelManager 正常工作
- [ ]
useGetIsLoggedIn()正确检测认证状态 - [ ] 受保护路由重定向未认证用户
- [ ]
refreshAccount()在创建交易前调用 - [ ] gas 限制适合交易类型
- [ ] 链 ID 来自
useGetNetworkConfig(),而非硬编码 - [ ] 交易跟踪显示 toast 通知
- [ ] 智能合约 ABI 正确加载(如果使用合约)