以开发一个friend.tech项目的子图为例子展示 subgraph 的开发和部署过程。
去中心化社交网络 friend.tech 建立在 Coinbase 的 L2 网络基础上,实现社交影响力的货币化;本节以开发一个 friend.tech 项目的子图为例子展示 subgraph 的开发和部署过程。
本节的关注点在graph的开发,了解 friend.tech 的项目的详细分析看这里。
合约信息:
https://basescan.org/address/0xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d4#code
\ 合约代码:
......
contract FriendtechSharesV1 is Ownable {
        ......
        event Trade(address trader, address subject, bool isBuy, uint256 shareAmount, uint256 ethAmount, uint256 protocolEthAmount, uint256 subjectEthAmount, uint256 supply);
        ......
    function buyShares(address sharesSubject, uint256 amount) public payable {   
            uint256 supply = sharesSupply[sharesSubject];                              
            require(supply > 0 || sharesSubject == msg.sender, "Only the shares' subject can buy the first share");
            uint256 price = getPrice(supply, amount);                                  
            uint256 protocolFee = price * protocolFeePercent / 1 ether;                
            uint256 subjectFee = price * subjectFeePercent / 1 ether;                  
            require(msg.value >= price + protocolFee + subjectFee, "Insufficient payment");
            sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] + amount;
            sharesSupply[sharesSubject] = supply + amount;                             
            emit Trade(msg.sender, sharesSubject, true, amount, price, protocolFee, subjectFee, supply + amount);
            (bool success1, ) = protocolFeeDestination.call{value: protocolFee}("");
            (bool success2, ) = sharesSubject.call{value: subjectFee}("");             
            require(success1 && success2, "Unable to send funds");                     
       }
       function sellShares(address sharesSubject, uint256 amount) public payable { 
            uint256 supply = sharesSupply[sharesSubject];                           
            require(supply > amount, "Cannot sell the last share");                 
            uint256 price = getPrice(supply - amount, amount);                      
            uint256 protocolFee = price * protocolFeePercent / 1 ether;             
            uint256 subjectFee = price * subjectFeePercent / 1 ether;               
            require(sharesBalance[sharesSubject][msg.sender] >= amount, "Insufficient shares");
            sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] - amount;
            sharesSupply[sharesSubject] = supply - amount;                          
            emit Trade(msg.sender, sharesSubject, false, amount, price, protocolFee, subjectFee, supply - amount);
            (bool success1, ) = msg.sender.call{value: price - protocolFee - subjectFee}("");
            (bool success2, ) = protocolFeeDestination.call{value: protocolFee}("");
            (bool success3, ) = sharesSubject.call{value: subjectFee}("");          
            require(success1 && success2 && success3, "Unable to send funds");      
       }
}
friend.tech 项目的智能合约相对简单,主要有两个重要的函数 buyShares 和 sellShares; 用户的交易行为触发了 Trade 事件, Trade 事件函数的签名记录了交易相关的信息, 即需要索引的数据。
\ 创建合约的区块
https://basescan.org/tx/0xa7eba644182d78c4568364e00b0320a9fde9c1fe779cdbec6941fb7443d14c01
yarn global add @graphprotocol/graph-cli
yarn global add @graphprotocol/graph-ts

✔ Protocol · ethereum                                                                                                                                    
✔ Product for which to initialize · subgraph-studio                                                                                                      
✔ Subgraph slug · FriendtechGraph                                                                                                                        
✔ Directory to create the subgraph in · FriendtechGraph
✔ Ethereum network · base
✔ Contract address · 0xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d4
✔ Fetching ABI from Etherscan
✖ Failed to fetch Start Block: Failed to fetch contract creation transaction 
✔ Do you want to retry? (Y/n) · false
✔ Start Block · 2430439
✔ Contract Name · FriendtechSharesV1
✔ Index contract events as entities (Y/n) · true
  Generate subgraph
  Write subgraph to directory
✔ Create subgraph scaffold
✔ Initialize networks config
✔ Initialize subgraph repository
✔ Install dependencies with yarn
✔ Generate ABI and schema types with yarn codegen
Add another contract? (y/n): 
Subgraph FriendtechGraph created in FriendtechGraph
第7步如果因为网络原因不能获取合约ABI文件,也可以使用本地文件。
\ 项目结构
cd FriendtechGraph
tree -L 1
├── abis
├── generated
├── networks.json
├── node_modules
├── package.json
├── schema.graphql
├── src
├── subgraph.yaml
├── tests
├── tsconfig.json
└── yarn.lock
定义需要索引的数据实体,以及实体之间的关系;详细可参考: Graph模式
type OwnershipTransferred @entity(immutable: true) {                                                                                                     
  id: Bytes!                                                                       
  previousOwner: Bytes! # address                                                  
  newOwner: Bytes! # address                                                    
  blockNumber: BigInt!                                                          
  blockTimestamp: BigInt!                                                       
  transactionHash: Bytes!                                                       
}                                                                               
type Trade @entity(immutable: true) {                                           
  id: Bytes!                                                                    
  trader: Bytes! # address                                                      
  subject: Bytes! # address                                                     
  isBuy: Boolean! # bool                                                        
  shareAmount: BigInt! # uint256                                                
  ethAmount: BigInt! # uint256                                                  
  protocolEthAmount: BigInt! # uint256                                          
  subjectEthAmount: BigInt! # uint256                                           
  supply: BigInt! # uint256                                                     
  blockNumber: BigInt!                                                          
  blockTimestamp: BigInt!                                                       
  transactionHash: Bytes!                                                       
}
从区块链事件中索引数据的具体逻辑, handleTrade 函数会在每次 Trade 事件触发时被调用,然后获取到链上数据存储到子图中。
import {                                                                        
  OwnershipTransferred as OwnershipTransferredEvent,                            
  Trade as TradeEvent                                                           
} from "../generated/FriendtechSharesV1/FriendtechSharesV1"                     
import { OwnershipTransferred, Trade } from "../generated/schema"               
export function handleOwnershipTransferred(                                     
  event: OwnershipTransferredEvent                                              
): void {                                                                       
  let entity = new OwnershipTransferred(                                        
    event.transaction.hash.concatI32(event.logIndex.toI32())                    
  )                                                                             
  entity.previousOwner = event.params.previousOwner                             
  entity.newOwner = event.params.newOwner                                       
  entity.blockNumber = event.block.number                                       
  entity.blockTimestamp = event.block.timestamp                                 
  entity.transactionHash = event.transaction.hash                               
  entity.save()                                                                 
}                                                                               
export function handleTrade(event: TradeEvent): void {                          
  let entity = new Trade(                                                       
    event.transaction.hash.concatI32(event.logIndex.toI32())                    
  )                                                                             
  entity.trader = event.params.trader                                           
  entity.subject = event.params.subject                                         
  entity.isBuy = event.params.isBuy                                             
  entity.shareAmount = event.params.shareAmount                                 
  entity.ethAmount = event.params.ethAmount                                     
  entity.protocolEthAmount = event.params.protocolEthAmount                     
  entity.subjectEthAmount = event.params.subjectEthAmount                       
  entity.supply = event.params.supply                                           
  entity.blockNumber = event.block.number                                       
  entity.blockTimestamp = event.block.timestamp                                 
  entity.transactionHash = event.transaction.hash                               
  entity.save()                                                                 
}
每次修改完subgraph.yaml 和scheme.graphql 文件后都要执行 yarn codegen,用来生成 generated 目录中对应的 AssemblyScript 文件。
xxx@Inspiron:~/tmp/FriendtechGraph$ yarn codegen
yarn run v1.22.5
$ graph codegen
  Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
  Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
  Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
  Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
  Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
  Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
  Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
✔ Apply migrations
✔ Load subgraph from subgraph.yaml
  Load contract ABI from abis/FriendtechSharesV1.json
✔ Load contract ABIs
  Generate types for contract ABI: FriendtechSharesV1 (abis/FriendtechSharesV1.json)
  Write types to generated/FriendtechSharesV1/FriendtechSharesV1.ts
✔ Generate types for contract ABIs
✔ Generate types for data source templates
✔ Load data source template ABIs
✔ Generate types for data source template ABIs
✔ Load GraphQL schema from schema.graphql
  Write types to generated/schema.ts
✔ Generate types for GraphQL schema
Types generated successfully
将 subgraph 编译为 WebAssembly 等待部署。
xxx@Inspiron:~/tmp/FriendtechGraph$ yarn build
yarn run v1.22.5
$ graph build
  Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
  Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
  Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
  Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
  Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
  Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
  Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
✔ Apply migrations
✔ Load subgraph from subgraph.yaml
  Compile data source: FriendtechSharesV1 => build/FriendtechSharesV1/FriendtechSharesV1.wasm
✔ Compile subgraph
  Copy schema file build/schema.graphql
  Write subgraph file build/FriendtechSharesV1/abis/FriendtechSharesV1.json
  Write subgraph manifest build/subgraph.yaml
✔ Write compiled subgraph to build/
Build completed: build/subgraph.yaml
可以把 subgraph 部署到本地,也可以使用托管服务,这里演示使用 graph 官网提供的托管服务——子图工作室 。

\ 连接钱包并签名后,就可以创建子图项目了。

\ 右边可以看到相关的操作命令:

\ 使用 AUTH & DEPLOY 栏的示例命令进行部署,因为已经编译过,下面跳过编译命令:
xxx@Inspiron:~/tmp/FriendtechGraph$ graph auth --studio fffff2e2e969c873d1daf88c27c2b238
Deploy key set for https://api.studio.thegraph.com/deploy/
xxx@Inspiron:~/tmp/FriendtechGraph$ graph deploy --studio friendtechgraph
Which version label to use? (e.g. "v0.0.1"): v0.0.1
  Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
  Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
  Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
  Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
  Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
  Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
  Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
✔ Apply migrations
✔ Load subgraph from subgraph.yaml
  Compile data source: FriendtechSharesV1 => build/FriendtechSharesV1/FriendtechSharesV1.wasm
✔ Compile subgraph
  Copy schema file build/schema.graphql
  Write subgraph file build/FriendtechSharesV1/abis/FriendtechSharesV1.json
  Write subgraph manifest build/subgraph.yaml
✔ Write compiled subgraph to build/
  Add file to IPFS build/schema.graphql
                .. QmU2ns3ySUcCQ7ZRcY4qUDnaToSwx1nxGuagFHBJWg9UFZ
  Add file to IPFS build/FriendtechSharesV1/abis/FriendtechSharesV1.json
                .. QmfA1DYEazwF9cx6rJy1hTj5ToVTz1GySGeaETJ25S7hJR
  Add file to IPFS build/FriendtechSharesV1/FriendtechSharesV1.wasm
                .. QmU3PEhQaZ2PqZiEfreGvSSSXAMhgVpbygbJbL8uTy9FLy
✔ Upload subgraph to IPFS
Build completed: QmW1eEAf6aeS11kjGgZFfA9Y1E1t3gNFmHYuvX1sZvbcbk
Deployed to https://thegraph.com/studio/subgraph/friendtechgraph
Subgraph endpoints:
Queries (HTTP):     https://api.studio.thegraph.com/query/61273/friendtechgraph/v0.0.1
\ 可以在子图工作室看到已经完成了部署,且数据已经完成了同步。

可以使用提供的 QUERY URL 在 GraphiQL 中查询数据:

\ 部署完后可以进行测试,如果想在将子图发布到网络之前对其进行测试,也可以在 Subgraph Playground 中执行此操作或查看日志。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
