> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tenderly.co/llms.txt
> Use this file to discover all available pages before exploring further.

# 如何从 Web3 Actions 访问 Node RPC

> 学习如何使用 Web3 Actions 和 Node RPC 查找由另一个合约部署的 具有相同字节码的合约。

在本教程中，我们将创建一个 Web3 Action，每次 `UniswapV2Factory` 部署新的 `UniswapV2Pair` 合约时都会运行。Web3 Action 将比较新部署合约的字节码与另一个 `UniswapV2Pair` 合约的字节码。在此示例中，我们将使用 USDC Pair 合约。

为了获取字节码，我们将使用 [Node RPC](/node-rpc/overview) 作为节点提供商。如果字节码匹配，Web3 Action 会将该 pair 的地址和合约的字节码存储在 Web3 Action 的 Storage 中。

如果您想跳过本教程，以下是完整的解决方案：

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}



type UniswapPair = {
  token0: string;
  token1: string;
  pair: string;
};

const USDC_PAIR_CONTRACT_ADDRESS = '0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc';

export const onPairCreatedEventEmitted: ActionFn = async (context: Context, event: Event) => {
  try {
    const txEvent = event as TransactionEvent;
    const newPair = await getPairCreatedEvent(txEvent);

    const gatewayURL = context.gateways.getGateway(Network.MAINNET);
    const provider = new ethers.providers.JsonRpcProvider(gatewayURL);

    const usdcPairContract = await provider.getCode(USDC_PAIR_CONTRACT_ADDRESS);
    const newPairContract = await provider.getCode(newPair.pair);

    if (usdcPairContract === newPairContract) {
      const pairContracts = await context.storage.getJson('PairContracts');
      pairContracts[newPair.pair] = newPairContract;
      await context.storage.putJson('PairContracts', pairContracts);
    }
  } catch (error) {
    console.error(error);
  }
};

const getPairCreatedEvent = async (txEvent: TransactionEvent): Promise<UniswapPair> => {
  const i = new ethers.utils.Interface(UniswapV2FactoryAbi);
  const pairCreatedTopic = i.getEventTopic('PairCreated');

  const pairCreatedEventLog = txEvent.logs.find(log => {
    return log.topics.find(topic => topic == pairCreatedTopic) !== undefined;
  });

  if (pairCreatedEventLog == undefined) {
    throw Error('PairCreatedEvent missing');
  }

  return i.decodeEventLog(
    'PairCreated',
    pairCreatedEventLog.data,
    pairCreatedEventLog.topics,
  ) as unknown as UniswapPair;
};
```

### 前提条件

<Note>
  Web3 Actions 的触发机制仅在触发器定义中使用的合约已在
  Tenderly 上验证时才有效。了解 [如何在 Tenderly 上验证合约
  ](/contract-verification/overview)。
</Note>

在此示例中，我们将使用 `UniswapV2Factory` 和 `UniswapV2Pair` 合约。由于这两个合约已在 Tenderly 上公开验证，因此无需手动验证。

导航到 [UniswapV2Factory](https://dashboard.tenderly.co/contract/mainnet/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f) 并点击 **Add to Project** 按钮将该合约添加到您的项目。对 [UniswapV2Pair](https://dashboard.tenderly.co/contract/mainnet/0xd849b2af570ffa3033973ea11be6e01b7ba661d9) 执行相同的步骤。

## 步骤 1：定义触发器

每当 `UniswapV2Factory` 合约的 [`createPair` 函数](https://developers.uniswap.org/contracts/v2/reference/smart-contracts/factory#createpair) 被调用时，会发生以下情况：

* 为代币对创建一个新的 [UniswapV2Pair](https://developers.uniswap.org/contracts/v2/reference/smart-contracts/pair) 合约（如果尚不存在）
* 发出一个 [`PairCreated`](https://developers.uniswap.org/contracts/v2/reference/smart-contracts/factory#paircreated) 事件

我们可以定义触发器，让每次触及合约的事务调用某个发出特定事件的函数时都运行 Web3 Action。

在我们的示例中，我们将编写在 `UniswapV2Factory` 合约发出 `PairCreated` 事件时触发的规则。换言之，每当部署新的 `UniswapV2Pair` 合约时触发。

触发器的配置如下所示，需要保存到 `tenderly.yaml` 文件中：

```yaml title="example.yaml" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
account_id: ''
actions:
  YOUR_USERNAME/YOUR_PROJECT_SLUG:
    runtime: v2
    sources: actions
    specs:
      uniswapNewPair:
        description: Runs when a new pair is created on uniswap
        function: uniswapActions:onPairCreatedEventEmitted
        execution_type: parallel
        trigger:
          type: transaction
          transaction:
            status:
              - mined
            filters:
              - network: 1
                eventEmitted:
                  contract:
                    address: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
                  name: PairCreated
project_slug: ''
```

## 步骤 2：定义 Web3 Action 逻辑

当我们的 Web3 Action 运行时，它接收两个输入：

* 一个 [`context`](/monitoring/web3-actions/references/context) 对象，提供对 Node RPC 和 Storage 的访问
* 一个 [`transactionEvent`](/monitoring/web3-actions/references/functions-events-triggers#transaction-event) 负载，包含触发 Web3 Action 的事务相关数据

我们将使用 `context` 对象访问 `gateways`，它将作为 Ethers 的节点提供商，并使用 Storage 存储匹配的字节码。

<Note>
  了解 [如何在 Web3 Actions 中使用 Node RPC
  ](/monitoring/web3-actions/references/node-rpc-access) 以访问任何
  受支持的网络（包括 Mainnet），而无需管理 URL 或密钥。
</Note>

至于 `transactionEvent` 负载，我们将使用它的 `logs` 属性查找对应于 `PairCreated` topic 的事件日志。

### 步骤 2.1：解码 PairCreated 事件

触发时，Web3 Action 需要在负载的日志中查找 `PairCreated` 事件。我们将这段代码放入 `getPairCreatedEvent` 函数。

要获取 `PairCreated` topic 的哈希值，我们需要使用 `UniswapV2Factory` 合约的 ABI。为此，前往 [Tenderly 中的](https://dashboard.tenderly.co/contract/mainnet/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f) `UniswapV2Factory` 合约，点击 **View ABI** 并将其复制/粘贴到 `actions` 目录中的本地文件。

<Note>
  在此浏览 [Web3 Action 的项目结构
  ](/monitoring/web3-actions/references/project-structure)。
</Note>

现在我们可以通过以下代码获取该 topic 的引用：

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const i = new ethers.utils.Interface(UniswapV2FactoryAbi);
const pairCreatedTopic = i.getEventTopic('PairCreated');
```

接下来，我们需要遍历事务日志并找到引用该 topic 的条目，如下所示。

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const pairCreatedEventLog = txEvent.logs.find(log => {
  return log.topics.find(topic => topic == pairCreatedTopic) !== undefined;
});
```

最后，我们将使用 Ethers 将日志条目解码为 `UniswapPair` 类型。

`getPairCreatedEvent` 函数的结果是一个包含以下内容的对象：

* 构成 pair 的代币地址 —— `token0` 和 `token1` 属性
* 已部署 `UniswapV2Pair` 合约的地址 —— `pair` 属性

## 步骤 3：比较字节码

现在我们有了新部署合约的地址（包含在 `UniswapPair` 类型的 `pair` 属性中），可以使用它获取合约的字节码。

我们将使用 `USDC UniswapV2Pair` 合约的字节码来判断新创建的合约字节码是否为有效的 `UniswapV2Pair` 合约。它们的字节码必须完全匹配。

要获取这些合约的字节码，我们需要使用 Ethers 的 `getCode` 函数。

要完成这些步骤，我们首先需要设置一个节点提供商，即 Node RPC：`gateways`。

通过访问 `context` 对象的 `gateways` 属性，我们可以调用 `getGateway()` 函数轻松获取我们期望网络（在我们的例子中是 `MAINNET`）的 JSON-RPC URL，并将其用作 Ethers 的提供商。

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const gatewayURL = context.gateways.getGateway(Network.MAINNET);
const provider = new ethers.providers.JsonRpcProvider(gatewayURL);
```

这将使我们能够获取新 pair 和 USDC pair 的字节码。

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const usdcPairContract = await provider.getCode(USDC_PAIR_CONTRACT_ADDRESS);
const newPairContract = await provider.getCode(newPair.pair);
```

如果匹配，我们希望以 `pair_address: bytecode` 的格式在项目 Storage 中名为 `PairContracts` 的 JSON 属性下存储新 pair 的字节码：

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
if (usdcPairContract === newPairContract) {
  const pairContracts = await context.storage.getJson('PairContracts');
  pairContracts[newPair.pair] = newPairContract;
  await context.storage.putJson('PairContracts', pairContracts);
}
```

让 Web3 Action 运行一段时间后，我们可以检查项目的 Storage 并查看创建的 pairs：

<figure>
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/web3-actions/how-to-access-web3-gateway-from-web3-actions-1.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=f28fc08829c5047fd416162aa0a02f39" alt="Tenderly Docs" width="1216" height="752" data-path="images/web3-actions/how-to-access-web3-gateway-from-web3-actions-1.webp" />

  <figcaption />
</figure>

键是新部署的 `UniswapV2Pair` 合约的地址，而值都相同，代表 `UniswapV2Pair` 合约的字节码。
