跳转到主要内容
Simulation RPC 兼容 Mainnet、Boba 和 Polygon,其他网络将陆续 集成。\ Simulation API 兼容所有 Tenderly 支持的 网络
您可以通过集成 Simulation RPC 或 Simulation API 将模拟引入到您的 dapp。

集成 Simulation API 的好处

通过集成 Simulation API/Simulation RPC,您可以
  1. 在用户签名并将交易提交上链之前,对其进行预演。
  2. 更清楚地看到潜在的回滚,避免因 gas 费而损失金钱。
  3. 更清楚地看到成功应用的结果,包括与用户相关的资产转移和状态变化。
  4. 基于模拟结果构建逻辑,向用户展示他们的交易是否带来了最佳价值,并提出改进建议。
以下是一些示例:
  • 如果您在构建类似钱包的应用,可以展示一笔交易如何转移 ERC-20 代币,让用户看到自己资金的流向。
  • 如果您在构建 DeFi 产品,您可以使用事件分析一笔交易是否满足用户在 UI 中设定的最优条件。
  • 如果您正在准备一份由若干交易组成的治理提案,可以做一次模拟捆绑,以查看提案的应用效果。
  • 您甚至可以将提案交易与几笔测试交易捆绑在一起,以验证您的提案按预期执行,并识别可能的盲点。

示例:向 Uniswap UI 添加模拟

在本示例中,我们将展示如何将 Simulation RPC 集成到 Uniswap UI 中,并显示沿兑换路径的具体代币跳转。这样,我们就能让用户更清楚地看到交易的效果,并帮助他们对是否继续做出明智的决定。

Simulation 组件需求

以下是模拟组件的需求:
  • 该组件应显示用户在签名并发送交易前后所持有的代币数量。
  • 每当交易数据发生变化时都应执行一次模拟,因此它需要接收 tradeallowedSlippage 参数。
  • 该组件必须跟踪:
    • 当前余额(兑换之前)。
    • 我们将用于计算新余额的 Swap 事件。
    • 我们将用于计算新余额的 Transfer 事件数组。

ConfirmSwapModal 中渲染 Simulation

向用户告知交易结果的最佳位置是 ConfirmSwapModal,正好在他们签名并发送交易之前。 为简洁起见,此处代码已省略:
example
export default function ConfirmSwapModal({
  /* ... */
}: {
  /* ... */
}) {
  // ...
  const modalHeader = useCallback(() => {
    return trade ? (
      <>
        <SwapModalHeader.../>
+        <Simulation trade={trade} recipient={null} allowedSlippage={allowedSlippage} />
      </>
    ) : null
  }, [/* ... */])
  //...

Simulation 组件的实现

该组件的逻辑位于 simulate 函数中:
  • 它会在 tradeprovider 参数变化时运行,因此每当兑换可能发生变化时,它都会更新并重新模拟。
  • 它调用 setOldBalances,获取输入和输出 ERC-20 代币的余额并将其存储在 oldBalances 状态变量中。
  • 它调用 simulateRPC,执行对 tenderly_simulateTranscation 的 Simulation RPC 调用,然后:
    • 遍历解码后的日志以查找 Transfer 事件,并将它们存储在组件的 transfers 状态变量中。
    • 遍历解码后的日志以查找 Swap 事件,并将其存储在组件的 swap 状态变量中。
最后,为渲染模拟结果,我们显示 SimulationDetails 组件,它是一个纯函数式组件。它计算旧状态和新状态,并基于 tradeswaptransfersoldBalances 渲染数据。
example.tsx
const Simulation = ({
  trade,
  allowedSlippage,
  recipient,
}: {
  trade: InterfaceTrade<Currency, Currency, TradeType> | undefined;
  recipient: string | null;
  allowedSlippage: Percent;
}) => {
  const { account, provider } = useWeb3React();
  const nativeCurrency = useNativeCurrency();

  const deadline = useTransactionDeadline();
  const signatureData = useERC20PermitFromTrade(trade, allowedSlippage, deadline);
  const args = useSwapCallArguments(
    trade,
    allowedSlippage,
    recipient,
    signatureData.signatureData,
    deadline,
    undefined,
  );

  const [oldBalances, setOldBalances] = useState<any[]>([]);
  const [transfers, setTransfers] = useState<any[]>([]);
  const [swap, setSwap] = useState<any>({});

  useEffect(() => {
    onSimulate();
  }, [trade, provider]);

  async function onSimulate() {
    setOldBalances([]);
    setSwap({});
    const { address, calldata, value } = args[0];
    const provider = RPC_PROVIDERS[SupportedChainId.MAINNET];

    await getCurrentBalances();
    await simulateRPC(account, address, calldata, value);
  }

  async function getCurrentBalances() {
    return Promise.all([
      provider.send('eth_getBalance', [account, 'latest']),
      provider.send('eth_call', [
        {
          to: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
          data: `0x70a0823100000000000000000000000000${stripX(account)}`,
        },
        'latest',
      ]),
    ]).then(oldBalances => {
      setOldBalances(oldBalances);
    });
  }

  async function simulateRPC(account: string, address: string, calldata: string, value: string) {
    RPC_PROVIDERS[SupportedChainId.MAINNET]
      .send('tenderly_simulateTransaction', [
        {
          from: account,
          to: address,
          data: calldata,
          value: stripHexZero(value),
        },
        'latest', // block number to simulate on
        null, // optional state overrides for involved contracts
      ])
      .then(simulationResponse => {
        console.log('Simulation Response', simulationResponse);
        setTransfers(simulationResponse.logs.filter((log: any) => log.name === 'Transfer'));
        setSwap(simulationResponse.logs.filter((log: any) => log.name === 'Swap')[0]);
      });
  }

  return (
    <>
      ...
      <SimulationDetails
        trade={trade}
        syncing={oldBalances.length == 0 && transfers.length == 0 && !!swap.inputs}
        transfers={transfers}
        oldBalances={oldBalances}
        swap={swap}
      />
    </>
  );
};