skip to content
Logo nickqiao's blog

L2BEAT源码解析

/ 6 min read

Table of Contents

产品分析

TVL

Untitled

其TVL列表分为3类

  • CBV - Canonically Bridged Value 锁定在以太坊上,跨到L2
  • EBV - Externally Bridged Value 锁定在其他链上,跨到L2
  • NMV - Natively Minted Value 直接在L2上mint的

Activity

Untitled_1

主要展示昨日TPS,7日环比变化,最高记录,30日总量

L2BEAT实现

TVL

Untitled_2

其实现入口位于l2beat/packages/backend/src/modules/tvl/api/TvlRouter.ts /api/tvl接口

取数据:

Untitled_3

其实现方式为从aggregated_reports表中直接查询数据,根据sql判断,该表存的是每小时聚合的数据,字段为:

Untitled_4

aggregated_reports表的数据源是report表,每小时从report表中查询数据,汇总到aggregated_reports中。report表字段为:

Untitled_5

大致流程:

在token.jsonc配置文件中,为每条链配置token列表,

如果是代币类型是EBV,则计算方式为totalsupply,查询该代币在当前链上合约的totalsupply,存储在total_supplies表;

Untitled_6

如果是NMV,则计算方式为circulatingSupply,从coingecko的api查询流通供应,存储在circulating_supplies表;

Untitled_7

如果是CBV,则计算方式为查询以太坊链上的官方桥合约对应代币的balance,存储在balances表中。

Untitled_8

还有一个定时任务从total_supplies,circulating_supplies,balances三张表中查询数据,再从coingecko的api获取币价,存储到report表中。

Activity

Untitled_9

其实现入口位于l2beat/packages/backend/src/modules/tvl/api/ActivityRouter.ts /api/activity接口

取数据:

Untitled_10 Untitled_11

其实现方式为从activity.daily_count_view表中直接查询数据,但未看到写入代码,根据表名推测应该是某个表的视图,根据sql判断,该视图为每天的tps数据。

进一步阅读代码,其实现逻辑为调用项目rpc,轮询最新区块,在packages/backend/src/modules/activity/SequenceProcessor.ts process函数中,把每个区块的交易数写入block表,其字段为:

Untitled_12

随后写入状态表sequence_processor,该表记录block表同步的最新区块状态:

Untitled_13

daily_count_view视图由block表创建。

BTCLayer2技术方案

tvl直接使用defillama的API,每日请求一次,缓存至内存。

tps采用L2beat的实现思路,创建定时任务,使用eth_getBlock轮询区块,计算出TPS,但不落数据库,直接缓存至内存中。如果项目方不支持eth jsonrpc的api,需要单独开发进行接入。

chainrpc
Merlin Chainhttps://rpc.merlinchain.io
Rootstockhttps://public-node.rsk.co https://rpc.rootstock.io/
Stacks不支持eth_getBlock
Bitlayerhttps://rpc.bitlayer.org https://rpc.bitlayer-rpc.com  https://rpc.ankr.com/bitlayer
B^Squarehttps://rpc.bsquared.network https://b2-mainnet.alt.technology
BEVMhttps://rpc-mainnet-1.bevm.io https://rpc-mainnet-2.bevm.io
BOBhttps://rpc.gobob.xyz/

L2BEAT源码部署

tvl模块

代码目录l2beat/backend

建库:

docker run -d --name=l2beat_postgres -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:14
docker exec -it l2beat_postgres psql -U postgres -c 'CREATE DATABASE l2beat_local'
docker exec -it l2beat_postgres psql -U postgres -c 'CREATE DATABASE l2beat_test'

创建环境变量.env文件(打开arbitrum eth的tvl统计功能)

LOCAL_DB_URL=postgresql://postgres:123456@localhost:5432/l2beat_local
TEST_DB_URL=postgresql://postgres:123456@localhost:5432/l2beat_test
FEATURES=tvl,!tvl.*,tvl.arbitrum,tvl.ethereum,status
ARBITRUM_RPC_URL=https://arb-mainnet.g.alchemy.com/v2/xxx
ARBITRUM_ETHERSCAN_API_KEY=xxx
ETHEREUM_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/xxx
ETHEREUM_ETHERSCAN_API_KEY=xxx
COINGECKO_API_KEY=xxx

配置目录l2beat/config

配置tokens.jsonc文件

{
"arbitrum": [
{
"symbol": "FRAX",
"address": "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F",
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
},
{
"symbol": "FXS",
"address": "0x9d2F299715D94d8A7E6F5eaa8E654E8c74a988A7",
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
},
]
}

运行yarn tokens,生成generated.json文件

{
"comment": "This file was autogenerated. Please do not edit it manually.",
"tokens": [
{
"id": "zrx-0x-protocol-token",
"name": "0x Protocol Token",
"coingeckoId": "0x",
"address": "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
"symbol": "ZRX",
"decimals": 18,
"deploymentTimestamp": 1502476756,
"coingeckoListingTimestamp": 1573689600,
"category": "other",
"iconUrl": "https://assets.coingecko.com/coins/images/863/large/0x.png?1696501996",
"chainId": 1,
"type": "CBV",
"formula": "locked"
},
{
"id": "arbitrum:frax-frax",
"name": "Frax",
"coingeckoId": "frax",
"address": "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F",
"symbol": "FRAX",
"decimals": 18,
"deploymentTimestamp": 1632766868,
"coingeckoListingTimestamp": 1718507527,
"category": "other",
"iconUrl": "https://coin-images.coingecko.com/coins/images/13422/large/FRAX_icon.png?1696513182",
"chainId": 42161,
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
},
{
"id": "arbitrum:frxeth-frax-ether",
"name": "Frax Ether",
"coingeckoId": "frax-ether",
"address": "0x178412e79c25968a32e89b11f63B33F733770c2A",
"symbol": "frxETH",
"decimals": 18,
"deploymentTimestamp": 1673211956,
"coingeckoListingTimestamp": 1718511102,
"category": "other",
"iconUrl": "https://coin-images.coingecko.com/coins/images/28284/large/frxETH_icon.png?1696527284",
"chainId": 42161,
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
},
{
"id": "arbitrum:fpi-frax-price-index",
"name": "Frax Price Index",
"coingeckoId": "frax-price-index",
"address": "0x1B01514A2B3CdEf16fD3c680a818A0Ab97Da8a09",
"symbol": "FPI",
"decimals": 18,
"deploymentTimestamp": 1677013204,
"coingeckoListingTimestamp": 1718514309,
"category": "other",
"iconUrl": "https://coin-images.coingecko.com/coins/images/24945/large/FPI_icon.png?1696524100",
"chainId": 42161,
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
},
{
"id": "arbitrum:fpis-frax-price-index-share",
"name": "Frax Price Index Share",
"coingeckoId": "frax-price-index-share",
"address": "0x3405E88af759992937b84E58F2Fe691EF0EeA320",
"symbol": "FPIS",
"decimals": 18,
"deploymentTimestamp": 1677013206,
"coingeckoListingTimestamp": 1718510810,
"category": "other",
"iconUrl": "https://coin-images.coingecko.com/coins/images/24944/large/FPIS_icon.png?1696524099",
"chainId": 42161,
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
},
{
"id": "arbitrum:fxs-frax-share",
"name": "Frax Share",
"coingeckoId": "frax-share",
"address": "0x9d2F299715D94d8A7E6F5eaa8E654E8c74a988A7",
"symbol": "FXS",
"decimals": 18,
"deploymentTimestamp": 1632766868,
"coingeckoListingTimestamp": 1718510434,
"category": "other",
"iconUrl": "https://coin-images.coingecko.com/coins/images/13423/large/Frax_Shares_icon.png?1696513183",
"chainId": 42161,
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
},
{
"id": "arbitrum:sfrxeth-staked-frax-ether",
"name": "Staked Frax Ether",
"coingeckoId": "staked-frax-ether",
"address": "0x95aB45875cFFdba1E5f451B950bC2E42c0053f39",
"symbol": "sfrxETH",
"decimals": 18,
"deploymentTimestamp": 1673211957,
"coingeckoListingTimestamp": 1718510623,
"category": "other",
"iconUrl": "https://coin-images.coingecko.com/coins/images/28285/large/sfrxETH_icon.png?1696527285",
"chainId": 42161,
"type": "EBV",
"formula": "totalSupply",
"bridgedUsing": {
"bridge": "Frax Ferry",
"slug": "fraxferry"
}
}
]
}

在config的project目录下配置layer2s,bridge,layer3,移除不需要的链

Untitled_15

项目运行

yarn start:dev

Untitled_16

访问http://localhost:3000 /api/tvl/aggregate?projectSlugs=arbitrum

运行过程中可能出现的问题:

  1. 由于网络问题,alchemy请求失败,可修改share文件夹下的httpclient.ts文件,使用本地代理
import fetch, { RequestInit, Response } from 'node-fetch'
import {HttpsProxyAgent} from 'https-proxy-agent';
export class HttpClient {
private readonly agent!: HttpsProxyAgent<string>;
constructor(private readonly defaultTimeoutMs = 10_000) {
this.agent = new HttpsProxyAgent("http://localhost:7890");
}
fetch(url: string, init?: RequestInit): Promise<Response> {
const requestInit: RequestInit = {
...init,
// 设置代理代理(如果存在)
agent: this.agent ?? init?.agent,
// 设置超时时间
timeout: (init && 'timeout' in init ? init.timeout : this.defaultTimeoutMs),
};
return fetch(url, requestInit);
}
}
  1. COINGECKO的api请求报错,demokey无法获取365日之前的数据,修改请求开始时间
Untitled_17

activity模块

features增加activity开关,必须配置STARKEX_API_KEY,可随意填写

FEATURES=tvl,!tvl.*,tvl.arbitrum,tvl.ethereum,status,activity,tvlCleaner
STARKEX_API_KEY=xxx

packages/backend/src/config/features/activity.ts配置eth起始区块

Untitled_18

packages/config/src/projects/layer2s/arbitrum.ts配置arb起始区块

Untitled_19