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

# 构建高性能应用的最佳实践

> 学习在 Monad 上构建高性能应用的最佳实践

## 配置 Web 托管以控制成本

* Vercel 和 Railway 提供了方便的无服务器平台来托管您的应用,相对于直接使用云提供商, 抽象了 web 托管的运维事务。您可能会为便利性付出溢价,尤其是在高流量时。
* AWS 和其他云提供商提供了更大的灵活性和商品化的定价。
* 在选择任何服务之前,请检查定价,并注意许多提供商在较低流量下提供亏本引流的定价,但一旦达到某个阈值, 就会收取更高的费用。
  * 例如,假设有一个 \$20 的套餐,包括每月 1 TB 的数据传输,超出后 \$0.20/GB。做一下算术就会发现第二 TB (以及之后)将花费 \$200。如果下一个套餐说"联系我们",不要假设下一个套餐会按 \$20 每 TB 收费。
  * 如果您正在构建高流量应用,并且不注意更便宜地提供静态文件,那么很容易超出亏本引流的层级, 并支付比预期高得多的费用。
* 对于 AWS 上的生产部署,请考虑:
  * Amazon S3 + CloudFront 用于静态文件托管和 CDN
  * AWS Lambda 用于无服务器函数
  * Amazon ECS 或 EKS 用于容器化应用
  * Amazon RDS 用于数据库需求
  * 这种设置通常为高流量应用提供精细的成本控制和可扩展性。

## 如果 gas 使用量是静态的,使用硬编码值代替 `eth_estimateGas` 调用

许多链上操作具有固定的 gas 成本。最简单的例子是原生代币的转账始终花费 21,000 gas,但还有许多其他情况。 这使得每笔交易调用 `eth_estimateGas` 变得没有必要。

请改用硬编码值,如[此处](/zh/developer-essentials/gas-pricing#set-the-gas-limit-explicitly-if-it-is-constant)所建议。 消除 `eth_estimateGas` 调用可以大幅加速钱包中的用户工作流程,并避免一些钱包在 `eth_estimateGas` 回滚时可能出现的不良行为(在链接页面中讨论)。

## 通过并发提交多个请求来降低 `eth_call` 延迟

串行发起多个 `eth_call` 请求会由于到 RPC 节点的多次往返而引入不必要的延迟。您可以并发发起许多 `eth_call`, 方法是将它们压缩为单个 `eth_call` 或提交一批调用。或者,您可能会发现切换到索引器更好。

### 将多个 `eth_call` 压缩为一个

* **Multicall:** Multicall 是一个实用智能合约,允许您将多个读取请求(`eth_call`)聚合为单个请求。 这对于同时获取代币余额、授权或合约参数等数据点特别有效。标准 `Multicall3` 合约部署在 Monad 主网和 Monad 测试网上的 [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://monadvision.com/address/0xcA11bde05977b3631167028862bE2a173976CA11)。 许多库都提供辅助函数来简化 multicall 的使用,例如 [viem](https://viem.sh/docs/contract/multicall.html)。 在[此处](https://www.multicall3.com)阅读有关 `Multicall3` 的更多信息。
* **自定义批处理合约:** 对于复杂的读取模式或标准 multicall 合约无法轻易处理的场景, 您可以部署一个自定义智能合约,在单个函数中聚合所需的数据,然后通过单个 `eth_call` 调用它。

<Note>
  Multicall 按序列执行调用,如您从[**此处**](https://monadvision.com/address/0xcA11bde05977b3631167028862bE2a173976CA11?tab=Contract#file-Multicall3.sol)的代码中可以看到。 因此,虽然使用 multicall 避免了到 RPC 服务器的多次往返,但仍然不建议将过多昂贵的调用放入一个 multicall 中。 一批调用(接下来解释)可以在 RPC 上并行执行。
</Note>

### 提交一批调用

大多数主要库支持将多个 RPC 请求批处理到单个消息中。

例如,`viem` 通过将 promise 数组作为单个批次提交来处理 `Promise.all()`:

```javascript theme={null}
const resultPromises = Array(BATCH_SIZE)
  .fill(null)
  .map(async (_, i) => {
    return await PUBLIC_CLIENT.simulateContract({
        address: ...,
        abi: ...,
        functionName: ...,
        args: [...],
      })
  })
const results = await Promise.all(resultPromises)
```

### 对读取密集型负载使用索引器

如果您的应用频繁查询历史事件或派生状态,请考虑使用索引器,如下所述。

## 使用索引器代替反复调用 `eth_getLogs` 来监听事件

以下是最流行的数据索引解决方案的快速入门指南。有关更多详情,请查看[索引器文档](/zh/tooling-and-infra/indexers/)。

### 使用 Allium

<Note>
  另请参见:[**Allium**](/zh/tooling-and-infra/indexers/common-data#allium)

  您将需要 Allium 帐户,可以在[此处](https://www.allium.so/contact)申请。
</Note>

* Allium Explorer
  * 提供基于 SQL 访问历史区块链数据(区块、交易、日志、trace 和合约)的区块链分析平台。
  * 您可以通过 [GUI](https://docs.allium.so/api/explorer/overview) 创建 Explorer API, 以查询和分析历史区块链数据。在[此处](https://app.allium.so/explorer/queries)为 API 创建查询时 (使用 `New` 按钮),从链列表中选择 `Monad Mainnet` 或 `Monad Testnet`。
  * 相关文档:
    * [Explorer 文档](https://docs.allium.so/app/overview)
    * [Explorer API](https://docs.allium.so/api/explorer/overview)
* Allium Datastreams
  * 通过 Kafka、Pub/Sub 和 Amazon SNS 提供实时区块链数据流(包括区块、交易、日志、trace、 合约和余额快照)。
  * 通过 [GUI](https://docs.allium.so/datastreams/overview) 为链上数据创建新流。 创建流时,从 `Select topics` 下拉菜单中选择相关的 `Monad Mainnet` 或 `Monad Testnet` 主题。
  * 相关文档:
    * [Datastreams 文档](https://docs.allium.so/datastreams/overview)
    * [Google Pub/Sub 入门](https://docs.allium.so/datastreams/pubsub)
* Allium Developers
  * 支持获取钱包交易活动和跟踪余额(原生、ERC20、ERC721、ERC1155)。
  * 对于请求体,使用 `monad_mainnet`(Monad 主网)或 `monad_testnet`(Monad 测试网)作为 `chain` 参数。
  * 相关文档:
    * [API 密钥设置指南](https://docs.allium.so/api/developer/wallets/overview#getting-started)
    * [钱包 API 文档](https://docs.allium.so/api/developer/wallets/overview)

### 使用 Envio HyperIndex

<Note>
  另请参见:[**Envio**](/zh/tooling-and-infra/indexers/indexing-frameworks#envio) 和[**指南:如何使用 Envio HyperIndex 构建代币转账通知机器人**](/zh/guides/indexers/tg-bot-using-envio)
</Note>

* 遵循[快速入门](https://docs.envio.dev/docs/HyperIndex/contract-import) 来创建索引器。在 `config.yaml` 文件中,使用网络 ID `10143` 来选择 Monad 测试网 (在下面的示例中使用)或网络 ID `143` 用于 Monad 主网。
* 示例配置
  * 示例 `config.yaml` 文件
    ```yaml title="config.yaml" lines theme={null}
    name: your-indexers-name
    networks:
    - id: 10143  # Monad Testnet
      # Optional custom RPC configuration - only add if default indexing has issues
      # rpc_config:
      #   url: YOUR_RPC_URL_HERE  # Replace with your RPC URL (e.g., from Alchemy)
      #   interval_ceiling: 50     # Maximum number of blocks to fetch in a single request
      #   acceleration_additive: 10  # Speed up factor for block fetching
      #   initial_block_interval: 10  # Initial block fetch interval size
      start_block: 0  # Replace with the block you want to start indexing from
      contracts:
      - name: YourContract  # Replace with your contract name
        address:
        - 0x0000000000000000000000000000000000000000  # Replace with your contract address
        # Add more addresses if needed for multiple deployments of the same contract
        handler: src/EventHandlers.ts
        events:
        # Replace with your event signatures
        # Format: EventName(paramType paramName, paramType2 paramName2, ...)
        # Example: Transfer(address from, address to, uint256 amount)
        # Example: OrderCreated(uint40 orderId, address owner, uint96 size, uint32 price, bool isBuy)
        - event: EventOne(paramType1 paramName1, paramType2 paramName2)
        # Add more events as needed
    ```
  * 示例 `EventHandlers.ts`
    ```tsx title="EventHandlers.ts" lines theme={null}
    import {
      YourContract,
      YourContract_EventOne,
    } from "generated";
    // Handler for EventOne
    // Replace parameter types and names based on your event definition
    YourContract.EventOne.handler(async ({ event, context }) => {
      // Create a unique ID for this event instance
      const entity: YourContract_EventOne = {
        id: `${event.chainId}_${event.block.number}_${event.logIndex}`,
        // Replace these with your actual event parameters
        paramName1: event.params.paramName1,
        paramName2: event.params.paramName2,
        // Add any additional fields you want to store
      };
      // Store the event in the database
      context.YourContract_EventOne.set(entity);
    })// Handler for EventOne
    // Replace parameter types and names based on your event definition
    YourContract.EventOne.handler(async ({ event, context }) => {
      // Create a unique ID for this event instance
      const entity: YourContract_EventOne = {
        id: `${event.chainId}_${event.block.number}_${event.logIndex}`,
        // Replace these with your actual event parameters
        paramName1: event.params.paramName1,
        paramName2: event.params.paramName2,
        // Add any additional fields you want to store
      };
      // Store the event in the database
      context.YourContract_EventOne.set(entity);
    })
    // Add more event handlers as needed
    ```
* 重要:网络下的 `rpc_config` 部分(查看 `config.yaml` 示例)是可选的, 仅当您在使用默认 Envio 设置时遇到问题时才应配置。此配置允许您:
  * 使用您自己的 RPC 端点
  * 配置块获取参数以获得更好的性能
* 相关文档:
  * [概述](https://docs.envio.dev/docs/HyperIndex/overview)

### 使用 GhostGraph

<Note>
  另请参见:[**Ghost**](/zh/tooling-and-infra/indexers/indexing-frameworks#ghost)
</Note>

* 相关文档:
  * [入门](https://docs.tryghost.xyz/category/-getting-started)
  * [在 Monad 测试网上设置 GhostGraph 索引器](/zh/guides/indexers/ghost#setting-up-ghostgraph-indexing)

### 使用 Goldsky

<Note>
  另请参见:[**Goldsky**](/zh/tooling-and-infra/indexers/common-data#goldsky)
</Note>

* Goldsky Subgraphs
  * 要部署 Goldsky subgraph,请遵循[此指南](https://docs.goldsky.com/subgraphs/deploying-subgraphs#from-source-code)。
  * 作为网络标识符,使用 `monad-mainnet`(Monad 主网)或 `monad-testnet`(Monad 测试网)。有关 subgraph 配置示例,请参考下面的 [The Graph 协议部分](#using-the-graph%E2%80%99s-subgraph)。
  * 有关查询 Goldsky subgraph 的信息,请参见 [GraphQL API 文档](https://docs.goldsky.com/subgraphs/graphql-endpoints)。
* Goldsky Mirror
  * 支持将链上数据直接流式传输到您的数据库。
  * 在为管道创建 `source` 时,`dataset_name` 字段的链名请使用 `monad_mainnet`(Monad 主网)或 `monad_testnet`(Monad 测试网)(查看下面的示例)
  * 示例 `pipeline.yaml` 配置文件
    ```yaml title="pipeline.yaml" lines theme={null}
    name: monad-testnet-erc20-transfers
    apiVersion: 3
    sources:
      monad_testnet_erc20_transfers:
        dataset_name: monad_testnet.erc20_transfers
        filter: address = '0x0' # Add erc20 contract address. Multiple addresses can be added with 'OR' operator: address = '0x0' OR address = '0x1'
        version: 1.2.0
        type: dataset
        start_at: earliest
    # Data transformation logic (optional)
    transforms:
      select_relevant_fields:
        sql: |
          SELECT
              id,
              address,
              event_signature,
              event_params,
              raw_log.block_number as block_number,
              raw_log.block_hash as block_hash,
              raw_log.transaction_hash as transaction_hash
          FROM
              ethereum_decoded_logs
        primary_key: id# Data transformation logic (optional)
    transforms:
      select_relevant_fields:
        sql: |
          SELECT
              id,
              address,
              event_signature,
              event_params,
              raw_log.block_number as block_number,
              raw_log.block_hash as block_hash,
              raw_log.transaction_hash as transaction_hash
          FROM
              ethereum_decoded_logs
        primary_key: id
    # Sink configuration to specify where data goes eg. DB
    sinks:
      postgres:
        type: postgres
        table: erc20_transfers
        schema: goldsky
        secret_name: A_POSTGRESQL_SECRET
        from: select_relevant_fields
    ```
  * 相关文档:
    * [Mirror 入门](https://docs.goldsky.com/mirror/create-a-pipeline#goldsky-cli)
    * [数据流指南](https://docs.goldsky.com/mirror/guides/)

### 使用 QuickNode Streams

<Note>
  另请参见:[**QuickNode Streams**](/zh/tooling-and-infra/indexers/common-data#quicknode)
</Note>

* 在您的 QuickNode 仪表板上,选择 `Streams` > `Create Stream`。在创建流的 UI 中, 在 Network 下选择 Monad Mainnet 或 Monad Testnet。或者,您可以使用 [Streams REST API](https://www.quicknode.com/docs/streams/rest-api) 来创建和管理流 — 使用 `monad-mainnet`(Monad 主网)或 `monad-testnet`(Monad 测试网)作为网络标识符。
* 您可以通过在流创建期间选择目的地来消费流。 支持的目的地包括 Webhooks、S3 存储桶和 PostgreSQL 数据库。 在[此处](https://www.quicknode.com/docs/streams/destinations)了解更多信息。
* 相关文档:
  * [入门](https://www.quicknode.com/docs/streams/getting-started)

### 使用 The Graph 的 Subgraph

<Note>
  另请参见:[**The Graph**](/zh/tooling-and-infra/indexers/indexing-frameworks#the-graph)
</Note>

* 网络 ID:使用 `monad-mainnet`(Monad 主网)或 `monad-testnet`(Monad 测试网)
* 示例配置
  * 示例 `subgraph.yaml` 文件
    ```yaml title="subgraph.yaml" lines theme={null}
    specVersion: 1.2.0
    indexerHints:
      prune: auto
    schema:
      file: ./schema.graphql
    dataSources:
      - kind: ethereum
        name: YourContractName # Replace with your contract name
        network: monad-testnet # Monad testnet configuration
        source:
          address: "0x0000000000000000000000000000000000000000" # Replace with your contract address
          abi: YourContractABI # Replace with your contract ABI name
          startBlock: 0 # Replace with the block where your contract was deployed/where you want to index from
        mapping:
          kind: ethereum/events
          apiVersion: 0.0.9
          language: wasm/assemblyscript
          entities:
            # List your entities here - these should match those defined in schema.graphql
            # - Entity1
            # - Entity2
          abis:
            - name: YourContractABI # Should match the ABI name specified above
              file: ./abis/YourContract.json # Path to your contract ABI JSON file
          eventHandlers:
            # Add your event handlers here, for example:
            # - event: EventName(param1Type, param2Type, ...)
            #   handler: handleEventName
          file: ./src/mapping.ts # Path to your event handler implementations
    ```
  * 示例 `mappings.ts` 文件
    ```tsx title="mappings.ts" lines theme={null}
    import {
      // Import your contract events here
      // Format: EventName as EventNameEvent
      EventOne as EventOneEvent,
      // Add more events as needed
    } from "../generated/YourContractName/YourContractABI" // Replace with your contract name, abi name you supplied in subgraph.yaml
    import {
      // Import your schema entities here
      // These should match the entities defined in schema.graphql
      EventOne,
      // Add more entities as needed
    } from "../generated/schema"
    /**
      * Handler for EventOne
      * Update the function parameters and body according to your event structure
      */
    export function handleEventOne(event: EventOneEvent): void {
      // Create a unique ID for this entity
      let entity = new EventOne(
        event.transaction.hash.concatI32(event.logIndex.toI32())
      )
      
      // Map event parameters to entity fields
      // entity.paramName = event.params.paramName
      
      // Example:
      // entity.sender = event.params.sender
      // entity.amount = event.params.amount
      // Add metadata fields
      entity.blockNumber = event.block.number
      entity.blockTimestamp = event.block.timestamp
      entity.transactionHash = event.transaction.hash
      // Save the entity to the store
      entity.save()
    }import {
      // Import your schema entities here
      // These should match the entities defined in schema.graphql
      EventOne,
      // Add more entities as needed
    } from "../generated/schema"
    /**
      * Handler for EventOne
      * Update the function parameters and body according to your event structure
      */
    export function handleEventOne(event: EventOneEvent): void {
      // Create a unique ID for this entity
      let entity = new EventOne(
        event.transaction.hash.concatI32(event.logIndex.toI32())
      )
      
      // Map event parameters to entity fields
      // entity.paramName = event.params.paramName
      
      // Example:
      // entity.sender = event.params.sender
      // entity.amount = event.params.amount
      // Add metadata fields
      entity.blockNumber = event.block.number
      entity.blockTimestamp = event.block.timestamp
      entity.transactionHash = event.transaction.hash
      // Save the entity to the store
      entity.save()
    }
    /**
      * Add more event handlers as needed
      * Format:
      * 
      * export function handleEventName(event: EventNameEvent): void {
      *   let entity = new EventName(
      *     event.transaction.hash.concatI32(event.logIndex.toI32())
      *   )
      *   
      *   // Map parameters
      *   entity.param1 = event.params.param1
      *   entity.param2 = event.params.param2
      *   
      *   // Add metadata
      *   entity.blockNumber = event.block.number
      *   entity.blockTimestamp = event.block.timestamp
      *   entity.transactionHash = event.transaction.hash
      *   
      *   entity.save()
      * }
      */
    ```
  * 示例 `schema.graphql` 文件
    ```graphql title="schema.graphql" lines theme={null}
    # Define your entities here
    # These should match the entities listed in your subgraph.yaml
    # Example entity for a generic event
    type EventOne @entity(immutable: true) {
      id: Bytes!
      
      # Add fields that correspond to your event parameters
      # Examples with common parameter types:
      # paramId: BigInt!              # uint256, uint64, etc.
      # paramAddress: Bytes!          # address
      # paramFlag: Boolean!           # bool
      # paramAmount: BigInt!          # uint96, etc.
      # paramPrice: BigInt!           # uint32, etc.
      # paramArray: [BigInt!]!        # uint[] array
      # paramString: String!          # string
      
      # Standard metadata fields
      blockNumber: BigInt!
      blockTimestamp: BigInt!
      transactionHash: Bytes!
    }
    # Add more entity types as needed for different events
    # Example based on Transfer event:
    # type Transfer @entity(immutable: true) {
    #   id: Bytes!
    #   from: Bytes!                  # address
    #   to: Bytes!                    # address
    #   tokenId: BigInt!              # uint256
    #   blockNumber: BigInt!
    #   blockTimestamp: BigInt!
    #   transactionHash: Bytes!
    # }# Example entity for a generic event
    type EventOne @entity(immutable: true) {
      id: Bytes!
      
      # Add fields that correspond to your event parameters
      # Examples with common parameter types:
      # paramId: BigInt!              # uint256, uint64, etc.
      # paramAddress: Bytes!          # address
      # paramFlag: Boolean!           # bool
      # paramAmount: BigInt!          # uint96, etc.
      # paramPrice: BigInt!           # uint32, etc.
      # paramArray: [BigInt!]!        # uint[] array
      # paramString: String!          # string
      
      # Standard metadata fields
      blockNumber: BigInt!
      blockTimestamp: BigInt!
      transactionHash: Bytes!
    }
    # Add more entity types as needed for different events
    # Example based on Transfer event:
    # type Transfer @entity(immutable: true) {
    #   id: Bytes!
    #   from: Bytes!                  # address
    #   to: Bytes!                    # address
    #   tokenId: BigInt!              # uint256
    #   blockNumber: BigInt!
    #   blockTimestamp: BigInt!
    #   transactionHash: Bytes!
    # }
    # Example based on Approval event:
    # type Approval @entity(immutable: true) {
    #   id: Bytes!
    #   owner: Bytes!                 # address
    #   approved: Bytes!              # address
    #   tokenId: BigInt!              # uint256
    #   blockNumber: BigInt!
    #   blockTimestamp: BigInt!
    #   transactionHash: Bytes!
    # }
    ```
* 相关文档:
  * [快速入门](https://thegraph.com/docs/en/subgraphs/quick-start/)

### 使用 thirdweb 的 Insight API

<Note>
  另请参见:[**thirdweb**](/zh/tooling-and-infra/indexers/common-data#thirdweb)
</Note>

* 提供广泛的链上数据的 REST API,包括事件、区块、交易、代币数据(如转账交易、余额和代币价格)、 合约详情等。
* 构造请求 URL 时,使用链 ID `143`(Monad 主网)或 `10143`(Monad 测试网)。
* 相关文档:
  * [入门](https://insight.thirdweb.com/reference)

## 如果快速连续发送多笔交易,请在本地管理 nonce

<Note>
  这仅适用于您手动设置 nonce 的情况。如果您将此委托给钱包,则无需担心。
</Note>

* `eth_getTransactionCount` 需要网络请求。如果您有来自同一钱包的多笔连续交易, 您应该实现本地 nonce 跟踪。

## 并发提交多笔交易

如果您要提交一系列交易,请实现并发交易提交以提高效率,而不是顺序提交。

之前:

```jsx lines theme={null}
for (let i = 0; i < TIMES; i++) {
  const tx_hash = await WALLET_CLIENT.sendTransaction({
    account: ACCOUNT,
    to: ACCOUNT_1,
    value: parseEther('0.1'),
    gasLimit: BigInt(21000),
    baseFeePerGas: BigInt(50000000000),
    chain: CHAIN,
    nonce: nonce + Number(i),
  })
}
```

之后:

```jsx lines theme={null}
const transactionsPromises = Array(BATCH_SIZE)
  .fill(null)
  .map(async (_, i) => {
    return await WALLET_CLIENT.sendTransaction({
      to: ACCOUNT_1,
      value: parseEther('0.1'),
      gasLimit: BigInt(21000),
      baseFeePerGas: BigInt(50000000000),
      chain: CHAIN,
      nonce: nonce + Number(i),
    })
  })
const hashes = await Promise.all(transactionsPromises)
```
