TheGraph 二 :subgraph 四大关键定义数据

TheGraph 二 :subgraph 四大关键定义数据
Photo by Uriel SC / Unsplash

我们以 Uniswap V2 的 subgraph 为例,来详细解释 subgraph 的四个关键定义数据。

一、SubgraphManifest

Uniswap V2 的 subgraph.yaml 文件就是其 SubgraphManifest,定义了这个 subgraph 的基本信息。

specVersion: 0.0.2
description: Uniswap is a decentralized protocol for automated token exchange on Ethereum.
repository: https://github.com/Uniswap/uniswap-v2-subgraph
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: Factory
    network: mainnet
    source:
      address: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
      abi: Factory
      startBlock: 10000835
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.4
      language: wasm/assemblyscript
      entities:
        - Pair
        - Token
      abis:
        - name: Factory
          file: ./abis/factory.json
        - name: Pair
          file: ./abis/pair.json
        - name: ERC20
          file: ./abis/ERC20.json
      eventHandlers:
        - event: PairCreated(indexed address,indexed address,address,uint256)
          handler: handlePairCreated
      file: ./src/mappings/factory.ts
  - kind: ethereum/contract
    name: Pair
    network: mainnet
    source:
      abi: Pair
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.4
      language: wasm/assemblyscript
      entities:
        - Pair
        - Token
      abis:
        - name: Pair
          file: ./abis/pair.json
        - name: Factory
          file: ./abis/factory.json
      eventHandlers:
        - event: Mint(indexed address,uint256,uint256)
          handler: handleMint
        - event: Burn(indexed address,uint256,uint256,indexed address)
          handler: handleBurn
        - event: Swap(indexed address,uint256,uint256,uint256,uint256,indexed address)
          handler: handleSwap
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
        - event: Sync(uint112,uint112)
          handler: handleSync
      file: ./src/mappings/core.ts

这个 manifest 定义了两个数据源:FactoryPair,分别对应 Uniswap V2 的核心合约。每个数据源都指定了网络、合约地址、ABI、起始区块等信息,以及相应的 mapping 函数。

二、DataSource

Factory 数据源为例:

- kind: ethereum/contract
    name: Factory
    network: mainnet
    source:
      address: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
      abi: Factory
      startBlock: 10000835

这里定义了如何从以太坊主网上的 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 地址获取 Factory 合约的数据,使用 Factory 的 ABI 解析数据,从区块 10000835 开始同步。

三、Mapping 函数

继续看 Factory 的 mapping 定义:

mapping:
      kind: ethereum/events
      apiVersion: 0.0.4
      language: wasm/assemblyscript
      entities:
        - Pair
        - Token
      abis:
        - name: Factory
          file: ./abis/factory.json
        - name: Pair
          file: ./abis/pair.json
        - name: ERC20
          file: ./abis/ERC20.json
      eventHandlers:
        - event: PairCreated(indexed address,indexed address,address,uint256)
          handler: handlePairCreated
      file: ./src/mappings/factory.ts

这里定义了 Factory 数据源的 mapping 函数。它监听 PairCreated 事件,当事件发生时,调用 handlePairCreated 函数处理事件数据。

handlePairCreated 函数在 ./src/mappings/factory.ts 文件中定义:

export function handlePairCreated(event: PairCreated): void {
  // load factory
  let factory = Factory.load(FACTORY_ADDRESS)
  if (factory === null) {
    factory = new Factory(FACTORY_ADDRESS)
    factory.pairCount = 0
    factory.totalVolumeETH = ZERO_BD
    factory.totalLiquidityETH = ZERO_BD
    factory.totalVolumeUSD = ZERO_BD
    factory.untrackedVolumeUSD = ZERO_BD
    factory.totalLiquidityUSD = ZERO_BD
    factory.txCount = ZERO_BI
    factory.mostLiquidTokens = []
  }
  factory.pairCount = factory.pairCount + 1
  factory.save()

  // create the tokens
  let token0 = Token.load(event.params.token0.toHexString())
  let token1 = Token.load(event.params.token1.toHexString())

  // fetch info if null
  if (token0 === null) {
    token0 = new Token(event.params.token0.toHexString())
    token0.symbol = fetchTokenSymbol(event.params.token0)
    token0.name = fetchTokenName(event.params.token0)
    token0.totalSupply = fetchTokenTotalSupply(event.params.token0)
    let decimals = fetchTokenDecimals(event.params.token0)
    // bail if we couldn't figure out the decimals
    if (decimals === null) {
      log.debug('mybug the decimal on token 0 was null', [])
      return
    }
    token0.decimals = decimals
    token0.derivedETH = ZERO_BD
    token0.tradeVolume = ZERO_BD
    token0.tradeVolumeUSD = ZERO_BD
    token0.untrackedVolumeUSD = ZERO_BD
    token0.totalLiquidity = ZERO_BD
    // token0.allPairs = []
    token0.txCount = ZERO_BI
  }

  // fetch info if null
  if (token1 === null) {
    token1 = new Token(event.params.token1.toHexString())
    token1.symbol = fetchTokenSymbol(event.params.token1)
    token1.name = fetchTokenName(event.params.token1)
    token1.totalSupply = fetchTokenTotalSupply(event.params.token1)
    let decimals = fetchTokenDecimals(event.params.token1)
    // bail if we couldn't figure out the decimals
    if (decimals === null) {
      return
    }
    token1.decimals = decimals
    token1.derivedETH = ZERO_BD
    token1.tradeVolume = ZERO_BD
    token1.tradeVolumeUSD = ZERO_BD
    token1.untrackedVolumeUSD = ZERO_BD
    token1.totalLiquidity = ZERO_BD
    // token1.allPairs = []
    token1.txCount = ZERO_BI
  }

  let pair = new Pair(event.params.pair.toHexString()) as Pair
  pair.token0 = token0.id
  pair.token1 = token1.id
  pair.liquidityProviderCount = ZERO_BI
  pair.createdAtTimestamp = event.block.timestamp
  pair.createdAtBlockNumber = event.block.number
  pair.txCount = ZERO_BI
  pair.reserve0 = ZERO_BD
  pair.reserve1 = ZERO_BD
  pair.trackedReserveETH = ZERO_BD
  pair.reserveETH = ZERO_BD
  pair.reserveUSD = ZERO_BD
  pair.totalSupply = ZERO_BD
  pair.volumeToken0 = ZERO_BD
  pair.volumeToken1 = ZERO_BD
  pair.volumeUSD = ZERO_BD
  pair.untrackedVolumeUSD = ZERO_BD
  pair.token0Price = ZERO_BD
  pair.token1Price = ZERO_BD

  // create the tracked contract based on the template
  PairTemplate.create(event.params.pair)

  // save updated values
  token0.save()
  token1.save()
  pair.save()
  factory.save()
}

这个函数处理 PairCreated 事件,创建了 FactoryTokenPair 实体,并设置它们的初始值。这些实体对应了 schema 中定义的数据类型。

四、Schema

Uniswap V2 的 schema 定义在 schema.graphql 文件中:

type Factory @entity {
  id: ID!
  pairCount: Int!
  totalVolumeUSD: BigDecimal!
  totalVolumeETH: BigDecimal!
  untrackedVolumeUSD: BigDecimal!
  totalLiquidityUSD: BigDecimal!
  totalLiquidityETH: BigDecimal!
  txCount: BigInt!
  mostLiquidTokens: [TokenDayData!]!
}

type Token @entity {
  id: ID!
  symbol: String!
  name: String!
  decimals: BigInt!
  totalSupply: BigInt!
  tradeVolume: BigDecimal!
  tradeVolumeUSD: BigDecimal!
  untrackedVolumeUSD: BigDecimal!
  txCount: BigInt!
  totalLiquidity: BigDecimal!
  derivedETH: BigDecimal!
  mostLiquidPairs: [PairDayData!]!
}

type Pair @entity {
  id: ID!
  token0: Token!
  token1: Token!
  reserve0: BigDecimal!
  reserve1: BigDecimal!
  totalSupply: BigDecimal!
  reserveETH: BigDecimal!
  reserveUSD: BigDecimal!
  trackedReserveETH: BigDecimal!
  token0Price: BigDecimal!
  token1Price: BigDecimal!
  volumeToken0: BigDecimal!
  volumeToken1: BigDecimal!
  volumeUSD: BigDecimal!
  untrackedVolumeUSD: BigDecimal!
  txCount: BigInt!
  createdAtTimestamp: BigInt!
  createdAtBlockNumber: BigInt!
  liquidityProviderCount: BigInt!
}

# ...

这里定义了 FactoryTokenPair 三个主要的实体类型,以及它们的字段。这些定义决定了 subgraph 的数据模型,也就是 mapping 函数的输出结构,以及 GraphQL API 的查询接口。

综上所述,通过 SubgraphManifest 定义数据源和 mapping 函数,通过 mapping 函数将区块链数据转换为 schema 定义的数据模型,最终生成符合 schema 定义的 GraphQL API,这就是一个完整的 subgraph 开发流程。

在实际开发中,我们主要需要关注 schema 的设计和 mapping 函数的实现。schema 决定了数据模型和 API 接口,mapping 函数决定了数据的转换逻辑。一个好的 subgraph 设计,应该能够抽象出清晰、合理的数据模型,并通过 mapping 函数高效、准确地完成数据转换。

Read more

ngrok本地调试原理及Telegram mini app cookie path 问题

ngrok本地调试原理及Telegram mini app cookie path 问题

在现代web开发中,本地调试是一个非常重要的环节。然而,当我们需要将本地开发的应用暴露到公网以便进行测试时,就会遇到一些挑战。本文将详细介绍如何使用ngrok实现内网穿透进行本地调试,特别是在Telegram小程序开发场景中的应用,以及可能遇到的常见问题及其解决方案。 ngrok原理 ngrok是一个反向代理工具,它可以将本地服务器安全地暴露到公网。下面是ngrok的工作原理: 1. 用户启动ngrok客户端,并指定要暴露的本地端口。 2. ngrok客户端与ngrok云服务建立安全的通道。 3. ngrok云服务生成一个公网可访问的URL。 4. 当外部请求到达这个URL时,ngrok云服务将请求通过安全通道转发到本地ngrok客户端。 5. 本地ngrok客户端将请求转发到指定的本地端口。 6. 本地服务器处理请求并返回响应,响应通过相同的路径返回给客户端。 Telegram小程序调试场景 在Telegram小程序开发中,我们经常需要使用ngrok来进行本地调试。以下是具体步骤: 1. 启动本地开发服务器(例如运行在localhost:3000)。

TypeScript:从架构分层设计到IOC和AOP

TypeScript:从架构分层设计到IOC和AOP

TypeScript作为JavaScript的超集,为开发者提供了强大的类型系统和面向对象编程能力。然而,要在大型项目中充分发挥TypeScript的优势,我们需要深入理解软件架构原则和设计模式。本文将探讨如何使用TypeScript构建一个健壮的应用架构,涵盖分层设计、常见设计模式、控制反转(IOC)和面向切面编程(AOP)等高级概念。 分层架构 分层架构是组织大型应用程序的常用方法。它有助于关注点分离,使得每一层都可以独立开发和测试。一个典型的分层架构包括: 1. 表现层(Presentation Layer) 2. 业务逻辑层(Business Logic Layer) 3. 数据访问层(Data Access Layer) 4. 数据库(Database) 让我们使用图表来可视化这个架构: 接下来,我们将探讨每一层中可能使用的设计模式,并通过TypeScript代码示例来说明如何实现这些模式。 表现层 表现层负责处理用户界面和用户交互。在这一层,我们经常使用MVC(Model-View-Controller)或MVVM(Model-View-ViewM

Jotai v2: React状态管理的新篇章

Jotai v2: React状态管理的新篇章

Jotai是一个为React应用设计的轻量级状态管理库。2023年3月,Jotai发布了v2.0版本,带来了许多新特性和改进。本文将深入探讨Jotai v2的使用方法、适用场景、设计理念、源码结构以及核心功能的实现原理。 版本信息 本文讨论的是Jotai v2.0.3版本,发布于2023年5月。你可以通过以下命令安装 npm install [email protected] 基本使用 Jotai的核心概念是"atom"。atom是最小的状态单位,可以存储任何JavaScript值。让我们看一个简单的例子: import { atom, useAtom } from 'jotai' // 创建一个atom const countAtom = atom(0) function Counter() { // 使用atom const [count, setCount] = useAtom(

加密货币交易所十二:安全性和风险控制

加密货币交易所十二:安全性和风险控制

在加密货币合约交易所中,安全性和风险控制是至关重要的。这不仅关系到交易所的声誉和用户的资产安全,也直接影响到整个加密货币生态系统的稳定性。本章将详细探讨合约交易所在安全性和风险控制方面的关键策略和实施方法。 多重签名机制 多重签名(MultiSig)是一种强大的安全机制,要求多个私钥来授权交易,大大降低了单点故障和内部欺诈的风险。 概念解释 多重签名是一种需要多个私钥来签署和授权交易的加密技术。例如,在一个 2-of-3 多重签名设置中,需要三个私钥中的任意两个来完成交易。 在合约交易所中的应用 热钱包管理: * 设置:通常采用 2-of-3 或 3-of-5 的多重签名方案。 * 应用:每次从热钱包转出大额资金时,需要多个管理员的授权。 冷钱包管理: * 设置:可能采用更严格的 3-of-5 或 4-of-7 方案。 * 应用:定期将热钱包中的多余资金转移到冷钱包时使用。 智能合约升级: * 设置:可能需要多个核心开发者和安全审计员的签名。 * 应用:在升级关键智能合约时,确保变更经过充分审核和授权。 实现考虑 密钥管理: * 使用硬件安全