web3 开发

418 阅读15分钟

项目开发中一般采取什么链

线上

开发的应用程序(App)可以依据多种链条(Blockchain)来进行设计和实现。链依据的选择通常取决于应用程序的需求、性质和目标。以下是一些常见的链依据以及它们的特点:

  1. 以太坊(Ethereum) :以太坊是最流行的智能合约平台之一,具有强大的开发生态系统和智能合约功能。它通常用于构建去中心化应用程序(DApps),数字资产和金融服务等。
  2. 比特币(Bitcoin) :比特币是第一个和最知名的加密货币,其区块链主要用于安全的价值传输和储存。一些应用程序可能选择利用比特币的安全性和稳定性,例如支付应用程序或数字资产钱包。
  3. 波场(Tron) :波场是另一个智能合约平台,其特点是高吞吐量和低成本的交易。它通常用于构建去中心化娱乐应用程序,例如游戏和内容分发平台。
  4. 币安智能链(Binance Smart Chain) :币安智能链是一个基于以太坊的智能合约平台,旨在提供更快的交易速度和较低的交易费用。许多去中心化金融(DeFi)应用程序选择在此链上运行。
  5. 柚子链(EOS) :柚子链是一个具有高性能和可扩展性的区块链平台,旨在支持大规模的商业应用程序和去中心化应用程序。
  6. 多链(Polkadot,Cosmos等) :多链平台允许不同区块链之间的互操作性和通信,使开发人员可以创建跨链应用程序和服务。

在选择链依据时,开发人员通常会考虑以下因素:

  • 性能和扩展性:链的吞吐量、交易速度和可扩展性是否满足应用程序的需求?
  • 安全性:链的安全性和共识机制是否足够保护用户的资产和数据?
  • 成本:链上的交易费用对于应用程序的用户是否合理?
  • 生态系统:链上是否有丰富的开发工具、文档和社区支持?
  • 特定功能:链是否提供应用程序所需的特定功能,如智能合约、跨链通信等?

综合考虑这些因素,开发团队可以选择最适合其应用程序需求的链依据。

  1. 去中心化金融(DeFi)应用程序可能更适合在以太坊(强大)或币安智能链(交易速度和较低的交易费用)等智能合约平台上开发,而游戏应用程序可能更适合在柚子链或波场等具有高吞吐量的链上开发。
  2. 比较高的吞吐量和低成本的交易,适用于游戏开发

测试

选择本地local或者goerli等测试链

const localChain: Chain = {
  id: 1337,
  name: "Local",
  network: "Local",
  nativeCurrency: {
    name: "Noah",
    symbol: "NOAH",
    decimals: 18,
  },
  rpcUrls: {
    default: {
      http: ["http://127.0.0.1:7545"],
    },
  },
};
[process.env.NEXT_PUBLIC_ENV === "development" ? localChain : goerli],

跨链桥怎么实现的

在前端实现跨链桥业务功能时,您可以采用以下步骤和技术:

  1. 选择合适的跨链桥协议:Polkadot、Cosmos、Wanchain、以太坊跨链桥等支持多链协议
  2. 直接用ethers.js,WAGMI等实现与合约交互
  3. 用户界面设计:包括但不限于提供资产锁定、解锁、跨链交易监控等功能的界面元素。
  4. 资产锁定和解锁:在用户界面中提供资产锁定和解锁功能。用户应该能够选择要锁定的资产、目标链和目标地址,并执行相应的操作来完成资产的锁定和解锁。
  5. 事件监测和交易确认:在前端实现跨链桥业务功能时,需要监测跨链桥协议的事件并等待交易确认。这些事件可能包括跨链交易的开始、完成、失败等。

监听跨链交易状态:使用以太坊客户端库提供的方法,例如 ethers.js 中的 waitForTransaction() 方法或 Web3.js 中的 getTransactionReceipt() 方法来监听跨链交易的状态变化。

import React, { useState } from 'react';
import { ethers } from 'ethers';

function CrossChainTransactionListener() {
  const [transactionHash, setTransactionHash] = useState('');
  const [transactionStatus, setTransactionStatus] = useState('');

  const provider = new ethers.providers.Web3Provider(window.ethereum);

  const handleCrossChainTransaction = async () => {
    // 创建跨链交易
    const transaction = { to: '0xYourTargetAddress', value: ethers.utils.parseEther('1.0') };
    const signer = provider.getSigner();
    const tx = await signer.sendTransaction(transaction);
    setTransactionHash(tx.hash);

    // 监听交易状态
    const receipt = await tx.wait();
    if (receipt.status === 1) {
      setTransactionStatus('Completed');
    } else {
      setTransactionStatus('Failed');
    }
  };

  return (
    <div>
      <button onClick={handleCrossChainTransaction}>Initiate Cross-Chain Transaction</button>
      {transactionHash && <p>Transaction Hash: {transactionHash}</p>}
      {transactionStatus && <p>Transaction Status: {transactionStatus}</p>}
    </div>
  );
}

export default CrossChainTransactionListener;

  1. 错误处理和安全性:在前端实现跨链桥业务功能时,需要考虑到可能出现的错误情况和安全性问题。确保前端能够处理跨链交易中可能发生的错误,并采取必要的安全措施来保护用户资产和数据的安全。

react项目中获取nft列表链上数据请求速度太慢

  1. 优化查询方式:确保您的数据查询方式是有效率的。使用合适的过滤器、排序和分页机制,以减少从链上获取数据的负担。如果可能的话,尽量避免一次性获取大量数据,而是分批次获取数据。
  2. 缓存数据:考虑在客户端使用缓存来存储已获取的数据,以减少重复请求。您可以使用浏览器的本地存储(如 localStorage 或 IndexedDB)或者 React 的状态管理库(如 Redux 或 Context API)来实现数据缓存。
  3. 使用分布式查询服务:考虑使用分布式查询服务来提高数据获取速度。这些服务通常会提供更快速的链上数据查询,同时也可以缓存数据以减少查询时间。一些常用的分布式查询服务包括 The Graph、Infura 和 Alchemy。
  4. 优化网络连接:确保您的网络连接稳定且延迟较低。使用可靠的网络提供商,并确保您的服务器和客户端之间的连接质量良好。
  5. 并行请求:如果您需要获取多个数据源的数据,考虑使用并行请求来提高效率。通过同时发起多个请求,并在所有请求完成后再更新界面,可以减少等待时间。
  6. 减少数据量:如果可能的话,尽量减少从链上获取的数据量。只请求您实际需要的数据,并考虑对数据进行压缩或分段处理以减少传输时间和成本。

The Graph、Infura 和 Alchemy的区别

  • Graph: 学习曲线复杂,需要付费
  • Infura: 中心化风险风险,半免费
  • Alchemy:
    • 费用: 使用 Alchemy 服务可能需要支付一定的费用,特别是在处理大量数据和高并发请求时。费用可能会成为一些项目的考虑因素。
    • 限制: Alchemy 的免费计划存在一些限制,如请求速率限制和并发连接数限制。对于一些大型项目来说,可能需要考虑升级到付费计划以获得更多的资源和功能。

为什么可以加速

Alchemy 通过优化节点、网络连接、缓存和预取等方面,提供了高性能和稳定的链上请求服务,从而加速了区块链应用程序的开发和部署过程。

具体优化列表请求代码

import React, { useState, useEffect } from 'react';
import AlchemyWeb3 from '@alch/alchemy-web3';
import MyNFTContractABI from './MyNFTContractABI.json';

function App() {
  const [web3, setWeb3] = useState(null);
  const [accounts, setAccounts] = useState([]);
  const [contractInstance, setContractInstance] = useState(null);
  const [nftList, setNftList] = useState([]);

  useEffect(() => {
    // 使用 Alchemy 提供的 WebSocket 或 HTTP 连接到以太坊网络
    const alchemyWeb3 = AlchemyWeb3.createAlchemyWeb3('ALCHEMY_API_URL');

    setWeb3(alchemyWeb3);

    // 获取用户账户信息
    alchemyWeb3.eth.getAccounts().then((accounts) => {
      setAccounts(accounts);
    });

    // 部署智能合约实例
    const contract = new alchemyWeb3.eth.Contract(MyNFTContractABI, 'CONTRACT_ADDRESS');
    setContractInstance(contract);
  }, []);

  useEffect(() => {
    // 获取 NFT 列表
    const getNFTList = async () => {
      try {
        const nftIds = await contractInstance.methods.getNFTIds().call();
        const nfts = await Promise.all(nftIds.map(id => contractInstance.methods.getNFT(id).call()));
        setNftList(nfts);
      } catch (error) {
        console.error('获取 NFT 列表时出错:', error);
      }
    };

    if (contractInstance) {
      getNFTList();
    }
  }, [contractInstance]);

  return (
    <div>
      <h1>使用 Alchemy 获取 NFT 列表示例</h1>
      <p>当前账户地址:{accounts[0]}</p>
      <ul>
        {nftList.map((nft, index) => (
          <li key={index}>
            NFT ID: {nft.id}, Owner: {nft.owner}, Token URI: {nft.tokenURI}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

单元测试需要编写的业务场景

在开发基于 Web3 的 React DApp 时,单元测试是确保代码质量和功能稳定性的重要手段之一。以下是一些建议的情况,您可能需要编写单元测试:

  1. 智能合约交互逻辑:如果您的 DApp 包含与智能合约的交互逻辑,例如发送交易、调用合约函数等,那么这些逻辑的单元测试至关重要。您可以使用测试框架(如Jest或Mocha)和模拟库(如Sinon)来编写针对智能合约交互逻辑的单元测试。
  2. Redux 或 Context 状态管理逻辑:如果您的 DApp 使用了 Redux 或 React Context 等状态管理库,那么对于状态管理逻辑的单元测试也是必要的。您可以编写测试来确保状态的正确更新、触发正确的行为以及处理异步操作的情况。
  3. UI 组件行为:对于 UI 组件的行为,尤其是那些包含交互逻辑的组件,如表单、按钮、模态框等,编写单元测试可以帮助确保它们的行为符合预期。您可以使用测试库(如React Testing Library或Enzyme)来编写针对 UI 组件行为的单元测试。
  4. 网络请求和错误处理:如果您的 DApp 包含与外部服务或链上节点的网络请求,那么对于网络请求和错误处理的逻辑也需要进行单元测试。您可以使用模拟库(如nock)来模拟网络请求,并编写测试来确保正确处理网络请求和错误情况。
  5. 边缘情况和错误路径:最后,您还应该编写针对边缘情况和错误路径的单元测试。这些测试可以帮助您发现和修复潜在的边界情况和错误,从而提高代码的健壮性和稳定性。

针对有一个名为 TokenContract 的智能合约,其中包含一个名为 transfer 的函数,用于转移代币。我们将编写针对这个函数的单元测试。

npm install ethers jest @types/jest --save-dev
// tokenContract.test.js

import { ethers } from 'ethers';

// 导入您的智能合约 ABI
const tokenContractABI = [
  // 合约函数 transfer
  {
    "constant": false,
    "inputs": [
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  }
];

// 定义智能合约地址
const contractAddress = '0x1234567890abcdef1234567890abcdef12345678';

// 设置测试提供者
const provider = new ethers.providers.JsonRpcProvider(); // 使用默认的 provider,例如 Infura

// 导入您的智能合约
const tokenContract = new ethers.Contract(contractAddress, tokenContractABI, provider);

describe('Token Contract', () => {
  it('should transfer tokens successfully', async () => {
    // 模拟参数
    const toAddress = '0xabcdef1234567890abcdef1234567890abcdef12';
    const amount = ethers.utils.parseEther('10'); // 转账金额,这里假设是 10 个代币

    // 调用 transfer 函数
    const tx = await tokenContract.transfer(toAddress, amount);

    // 等待交易被确认
    await tx.wait();

    // 检查转账是否成功
    const balance = await tokenContract.balanceOf(toAddress);
    expect(balance).toEqual(amount);
  });

  it('should reject transfer if amount exceeds balance', async () => {
    // 模拟参数
    const toAddress = '0xabcdef1234567890abcdef1234567890abcdef12';
    const amount = ethers.utils.parseEther('100'); // 转账金额,假设大于账户余额

    // 调用 transfer 函数,预期应该抛出异常
    await expect(tokenContract.transfer(toAddress, amount)).rejects.toThrow();
  });

  it('should emit Transfer event', async () => {
    // 模拟参数
    const toAddress = '0xabcdef1234567890abcdef1234567890abcdef12';
    const amount = ethers.utils.parseEther('1'); // 转账金额,假设为 1 个代币

    // 监听 Transfer 事件
    const filter = tokenContract.filters.Transfer(null, toAddress, null);
    tokenContract.on(filter, (from, to, value) => {
      // 检查事件参数是否正确
      expect(from).toEqual(tokenContract.signer.address);
      expect(to).toEqual(toAddress);
      expect(value).toEqual(amount);
    });

    // 调用 transfer 函数
    await tokenContract.transfer(toAddress, amount);

    // 停止监听事件
    tokenContract.off(filter);
  });
});

钱包连接

  1. 状态管理:在 React 应用中,状态管理是至关重要的。您需要确保钱包连接状态、当前账户以及网络状态等信息能够在整个应用中进行有效的管理和同步。
  2. 事件处理:钱包连接过程中可能涉及到各种事件,如连接状态的变化、账户切换等。您需要编写相应的事件处理逻辑,并确保应用能够正确响应这些事件。
  3. 错误处理:在钱包连接过程中,可能会出现各种错误情况,如用户拒绝连接、网络断开等。您需要编写相应的错误处理逻辑,并向用户提供友好的错误提示。
  4. 界面更新:钱包连接状态发生变化时,您需要确保应用界面能够及时更新以反映最新的状态。这涉及到 React 组件的重新渲染和状态更新。
import React, { useState, useEffect } from 'react';
import Web3 from 'web3';

function WalletConnector() {
  const [web3, setWeb3] = useState(null);
  const [currentAccount, setCurrentAccount] = useState('');
  const [networkId, setNetworkId] = useState(0);
  const [error, setError] = useState('');

  // 连接钱包
  const connectWallet = async () => {
    try {
      if (window.ethereum) {
        const newWeb3 = new Web3(window.ethereum);
        setWeb3(newWeb3);

        // 请求用户授权连接钱包
        await window.ethereum.request({ method: 'eth_requestAccounts' });

        // 获取当前账户
        const accounts = await newWeb3.eth.getAccounts();
        setCurrentAccount(accounts[0]);

        // 监听账户变化
        window.ethereum.on('accountsChanged', (newAccounts) => {
          setCurrentAccount(newAccounts[0]);
        });

        // 获取当前网络 ID
        const networkId = await newWeb3.eth.net.getId();
        setNetworkId(networkId);
      } else {
        throw new Error('Web3 provider not found');
      }
    } catch (error) {
      setError(error.message);
    }
  };

  // 断开钱包连接
  const disconnectWallet = () => {
    if (web3 && web3.currentProvider && web3.currentProvider.disconnect) {
      web3.currentProvider.disconnect();
      setWeb3(null);
      setCurrentAccount('');
      setNetworkId(0);
      setError('');
    }
  };

  useEffect(() => {
    // 监听网络变化
    const handleNetworkChange = (networkId) => {
      setNetworkId(networkId);
    };
    if (web3) {
      web3.eth.net.on('networkChanged', handleNetworkChange);
    }

    return () => {
      if (web3) {
        web3.eth.net.removeListener('networkChanged', handleNetworkChange);
      }
    };
  }, [web3]);

  return (
    <div>
      {!web3 ? (
        <button onClick={connectWallet}>Connect Wallet</button>
      ) : (
        <>
          <p>Connected Account: {currentAccount}</p>
          <p>Network ID: {networkId}</p>
          <button onClick={disconnectWallet}>Disconnect Wallet</button>
        </>
      )}
      {error && <p>Error: {error}</p>}
    </div>
  );
}

export default WalletConnector;

其他

实现 NFT 创建与部署

  • 使用智能合约平台(如 Ethereum、Binance Smart Chain 等)创建 NFT 合约。
  • 编写智能合约代码,编写一个符合 ERC-721 或 ERC-1155 标准的智能合约,定义 NFT 的结构、功能和行为,包括 NFT 的元数据、所有者、转移功能等。
  • 编写测试用例,并使用测试工具对 NFT 合约进行测试。确保合约的各项功能和行为符合预期,并且能够正确处理各种情况和边界条件。
  • 开发后端服务,用于上传文件、调用智能合约部署 NFT。

token 以太坊上的 ERC-20

防范安全漏洞和攻击的建议:

  1. 估算 gas 费用

    • 在发送交易时,确保正确估算 gas 费用,以避免出现 gas 耗尽或交易失败的情况。
  2. 避免重放攻击

    • 使用适当的 nonce 和签名机制,以防止重放攻击和交易重复执行。
import React, { useState, useEffect } from 'react';
import Web3 from 'web3';
import { ethers } from 'ethers';

function SignedTransaction() {
  const [web3, setWeb3] = useState(null);
  const [account, setAccount] = useState('');
  const [nonce, setNonce] = useState('');
  const [txHash, setTxHash] = useState('');
  const [error, setError] = useState('');

  useEffect(() => {
    // 初始化 Web3
    const initWeb3 = async () => {
      try {
        if (window.ethereum) {
          const newWeb3 = new Web3(window.ethereum);
          setWeb3(newWeb3);
          // 请求账户授权
          const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
          setAccount(accounts[0]);
        } else {
          throw new Error('Web3 provider not found');
        }
      } catch (error) {
        console.error('Error initializing web3', error);
        setError(error.message);
      }
    };
    initWeb3();
  }, []);

  useEffect(() => {
    // 获取账户的 nonce
    const fetchNonce = async () => {
      if (web3 && account) {
        const newNonce = await web3.eth.getTransactionCount(account);
        setNonce(newNonce);
      }
    };
    fetchNonce();
  }, [web3, account]);

  // 处理交易签名和发送
  const handleTransaction = async () => {
    try {
      if (!web3 || !account) {
        throw new Error('Web3 not initialized or account not found');
      }
      // 构造交易对象
      const txObject = {
        from: account,
        to: '0xRecipientAddress', // 接收地址
        value: web3.utils.toWei('1', 'ether'), // 转账金额
        nonce: nonce, // 设置 nonce
        gasPrice: web3.utils.toHex(web3.utils.toWei('10', 'gwei')), // 设置 gas price
        gasLimit: web3.utils.toHex(21000), // 设置 gas limit
      };
      // 签名交易
      const signedTx = await web3.eth.accounts.signTransaction(txObject, '0xPrivateKey');
      // 发送签名交易
      const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
      setTxHash(receipt.transactionHash);
    } catch (error) {
      console.error('Error sending transaction', error);
      setError(error.message);
    }
  };

  return (
    <div>
      <h2>Signed Transaction</h2>
      <button onClick={handleTransaction}>Send Transaction</button>
      {txHash && <p>Transaction Hash: {txHash}</p>}
      {error && <p>Error: {error}</p>}
    </div>
  );
}

export default SignedTransaction;

在上面的示例中,我们首先使用 web3.eth.getTransactionCount() 方法获取账户的 nonce,并将其保存在状态中。然后,我们构造了一个包含 nonce 的交易对象,并使用账户的私钥对交易进行签名。最后,我们使用 web3.eth.sendSignedTransaction() 方法发送签名交易。

通过在交易中包含正确的 nonce 和使用签名机制,我们可以有效地防止重放攻击和交易重复执行。每笔交易的 nonce 都必须是唯一的,并且使用签名机制可以确保交易的完整性和不可篡改性。

  1. 权限控制

    • 对于需要进行权限控制的操作,确保在智能合约中实现了正确的权限控制策略,并在前端应用程序中进行相应的验证和控制。
  2. 参数校验

    • 在与智能合约进行交互时,始终进行参数校验和验证,确保输入的数据符合预期并且没有异常情况。
// 参数校验和验证 
const intValue = parseInt(value); 
if (isNaN(intValue) || intValue < 0 || intValue > 100) {
    setError('Value must be between 0 and 100'); 
    return; 
}
// 调用智能合约方法 
await contract.methods.set(intValue).send({ from: web3.eth.accounts[0] });

React 应用程序中调用智能合约函数时如何管理 Gas 费用、设置 Gas 限制和 Gas 价格等参数:

import React, { useState, useEffect } from 'react';
import Web3 from 'web3';
import MyContractABI from './MyContractABI.json';

function App() {
  const [web3, setWeb3] = useState(null);
  const [accounts, setAccounts] = useState([]);
  const [contractInstance, setContractInstance] = useState(null);

  useEffect(() => {
    const initWeb3 = async () => {
      // 连接到以太坊网络
      if (window.ethereum) {
        const web3Instance = new Web3(window.ethereum);
        setWeb3(web3Instance);
        try {
          // 请求用户授权
          await window.ethereum.enable();
          const accounts = await web3Instance.eth.getAccounts();
          setAccounts(accounts);

          // 部署智能合约实例
          const contract = new web3Instance.eth.Contract(MyContractABI, 'CONTRACT_ADDRESS');
          setContractInstance(contract);
        } catch (error) {
          console.error('用户拒绝授权:', error);
        }
      } else {
        console.error('请安装 MetaMask 插件');
      }
    };

    initWeb3();
  }, []);

  const handleButtonClick = async () => {
    try {
      // 估算 Gas 费用
      const gasEstimate = await contractInstance.methods.myFunction().estimateGas();

      // 设置 Gas 价格
      const gasPrice = await web3.eth.getGasPrice();

      // 设置 Gas 限制
      const gasLimit = Math.max(gasEstimate, 100000); // 限制为估算值或100000 Gas

      // 发送交易
      await contractInstance.methods.myFunction().send({
        from: accounts[0],
        gas: gasLimit,
        gasPrice: gasPrice
      });
    } catch (error) {
      console.error('调用智能合约函数时出错:', error);
    }
  };

  return (
    <div>
      <h1>Gas 费用、设置 Gas 限制和 Gas 价格参数业务示例</h1>
      <p>当前账户地址:{accounts[0]}</p>
      <button onClick={handleButtonClick}>调用智能合约函数并发送交易</button>
    </div>
  );
}

export default App;