Aptos数据分析师指南:供应量与交易量(第三部分)

本文深入探讨了Aptos区块链上同质化资产(Fungible Assets, FA)和币(Coins)的供应量和交易量的计算方法。文章详细介绍了不同类型的供应量(最大供应量、总供应量、流通供应量)以及如何通过链上数据跟踪和分析FA及Coins的供应和交易活动,并讨论了将交易量转换为单条记录的方法,最后,文章还讨论了循环供应的一些想法,并推荐使用FA标准。

作者:Ying Wu, Data Engineer @ Aptos Labs

供应量和交易量是任何类似货币的资产的两个重要指标。供应量用于诸如完全稀释价值 (FDV) 之类的计算以及跟踪通货膨胀。交易量是市场活动的关键指标,并且在与价格配对时,可以阐明潜在的供需动态。

以下内容将交替使用 Fungible Assets (FA) 和 Coins,背景信息可以在之前的帖子中的“Coins vs Fungible Asset”部分找到。

供应量

供应量可以定义为(从最大到最小)

  1. 可以铸造的资产的最大数量(最大供应量)
  2. 链上资产的数量(总供应量)
  3. 链上可以随时转移的资产数量(流通供应量)

除非另有说明,否则在提到供应量时,我们指的是总供应量。

最大供应量几乎总是 2¹²⁸-1 (340282366920938463463374607431768211455),因为它在 Rust/Move 中表示为 u128

对于给定的 coin/FA,有一组有限的原生操作

  • Mint:创建更多
  • Burn:移除
  • Transfer:转移一定数量的所有权,将为每个 Store 发出 Deposit 和 Withdrawal 事件,类似于复式记账
  • Freeze:默认实现阻止从 CoinStore/FungibleStore 进行转移
  • Create Store:创建 CoinStore/FungibleStore(通常是隐式完成的)
  • Update uri / metadata:更改名称、小数位数等(很少进行)

显示资产操作的图表

Mints 和 burns 会影响总供应量,但是,coins 也可以发送到没有所有者的虚拟 burn 地址(最常见的是 0xfff…fff,但也可能是 0x00xdead),或者被冻结,从而影响流通供应量。

FA 允许使用函数Hook扩展上述操作。

Coin 供应量

Coin 是旧标准,并且有 3 种方式可以表示供应量。Coin 元数据可以在 0x1::coin::CoinInfo<Type> 资源中找到

可以在以下位置找到供应量

  1. 作为一个整数 ex

大多数 coins 在 CoinInfo 中都有供应量跟踪

  1. 未定义 ex

有些 coins 没有供应量跟踪

  1. 在一个聚合器中

在主网和测试网上,只有 APT 使用此功能。实际值存储为 table items

APT Coins 使用聚合器进行跟踪

由于 gas 燃烧,几乎每个交易都在此Handle上进行了 table item 的更改(ex

几乎每个用户交易都更改了表示 APT 供应量的 table item

可以在此处找到查询聚合器的示例 SQL。此外,使用类型 0x1::aptos_coin::AptosCoin 执行 supply 视图函数也会提供 APT 供应量。

Fungible Asset (FA) 供应量

由于 FA 的流通供应量由资源 0x1::fungible_asset::Supply (ex) 或 0x1::fungible_asset::ConcurrentSupply (ex) 跟踪,因此更容易计算。

总供应量

如果 FA 不是从 coin 迁移而来(原生 FA),则总供应量就是 FA 的供应量。但是,如果 FA 表示迁移的 coin,则总供应量将是 coin 供应量 +(迁移的)FA 供应量。为了区分原生 FA 和迁移的 FA,请查找 0x1::coin::PairedCoinType 资源,该资源保存着 coin 的地址(例如,FACoin)。

SELECT
  fa_address AS asset_type_fa,
  '0x' || LPAD(LTRIM(JSON_VALUE(r.data, '$.type.account_address'), '0x'), 64, '0') || '::' ||
  SAFE_CONVERT_BYTES_TO_STRING(FROM_HEX(LTRIM(JSON_VALUE(r.data, '$.type.module_name'), '0x'))) || '::' ||
  SAFE_CONVERT_BYTES_TO_STRING(FROM_HEX(LTRIM(JSON_VALUE(r.data, '$.type.struct_name'), '0x'))) AS asset_type_coin,
FROM (
  SELECT address AS fa_address, ANY_VALUE(resource) AS data,
  FROM `bigquery-public-data.crypto_aptos_mainnet_us.resources`
  WHERE 1=1
  AND address = '0xcd70630fb90cab716ab01a7884821f86dceb1bbb09a89683b5c22c5462503f51'
  AND type_str = '0x1::coin::PairedCoinType'
  GROUP BY 1
) r

coin 和 FA 之间的映射之前已经介绍过(参见第 2 部分中的“查找迁移的 fungible assets”部分)。

交易量

通常,在报告交易量时,我们试图捕获链上转移交易量(而不是中心化交易所内部的交易量)。最直接的方法是利用 CoinStore / FungibleStore 的 withdrawal 或 deposit 事件,并加总以获得转移的数量。

交易量可能会因 mint(deposit)或 burn(withdraw)而膨胀。由于 burns 较少见,因此 SUM(withdraw_amount) 通常是一个足够好的近似值。

Coin 交易量

对于普通用户,Coins 几乎总是存储在 CoinStore 中。从 CoinStore 的 withdraws 和 deposits 将作为事件发出,其中包含一个 amount 和一个特殊的 guid 参数,该参数用于映射到发出该事件的 CoinStore 资源(Dune 中的示例逻辑)。

智能合约有时会使用自定义的 coin 持有者(实现为资源或 table items)。当从 CoinStore 转移到自定义的 coin 持有者时,withdrawal 事件将被记录在 CoinStore 上,但自定义的 coin 持有者不必记录 deposit 事件。

研究这些总 deposit 量和总 withdraw 量的加总不平衡的转移,一些最常见的情况是

  • Mints 和 Burns(通常是桥接或迁移到 FA)
  • 往返于 dex 交换和质押(使用自定义的 coin 持有者)

也可能存在 coins 从一个自定义持有者转移到另一个自定义持有者而没有 CoinStore 事件的情况(ex

这使得全面跟踪 coin 交易量非常具有挑战性。

Fungible asset 交易量

对于 fungible assets (FA),我们也会获得 withdraw 和 deposit 事件,但现在自定义的 coin 持有者都必须是 FungibleStore 的扩展,因此所有转移都会发出事件。因此,唯一不平衡的转移是 mints 和 burns。值得庆幸的是,这些可以通过供应量的变化来推断。对于迁移的 FA,mints 和 burns 表示 coin 和 FA 之间的迁移。

除了拥有数量之外,FA 的事件还将具有 FungibleStore 的地址。针对 FungibleStore 进行查找(由于商店中的余额发生变化,这些更改在同一交易中作为写入集更改发出)将给出资产的类型。当删除 FungibleStore 时会出现一种极端情况,因为发出的更改只是删除。为了解决这个问题,解析在 FungibleStore 删除时发出的 0x1::fungible_asset::FungibleStoreDeletion 事件(在 2025 年初引入),该事件包含资产和所有者的类型。

例如。状态键哈希 0x3e4e56d0586d4b0677b66f116af6e17594bb97c51f16f7f45803f3644a5e741a 已被删除(索引 8)。查看之前的状态,此状态键哈希包含一个 FungibleStore(索引 2),其中包含余额和元数据(CELLANA FA 地址)。在删除交易中,事件 2 具有删除事件,其中包含与所有者地址相同的 CELLANA FA 地址(先前状态下的索引 3)。

将交易量转换为单式记账

跟踪 deposit 和 withdrawal 事件类似于跟踪股票的买入和卖出。出于分析目的,我们通常希望数据以单式记账的形式进行结构化(表中的每一行都具有:to、from、amount 和资产类型)。这类似于为每个股票交易分配成本基础。

在 Aptos 上,一个交易中可能会发生多次转移。例如,空投交易可以在一个交易中将资产发送到数百个帐户。下面,我将介绍 3 种可能的情况以及如何将它们转换为单式记账。

点对点转移很简单,因为只有一个发送者和接收者

1 对 1 转移

Aptos 上的大多数稳定币交易都是 1 对多。这是因为 DeFi 平台会收取费用,因此在其中进行交互会有两个 deposits,一个 deposit 到费用帐户,一个 deposit 到 DeFi 产品帐户。下面是一个收取 3% 费用的示例。1 对多转移也会发生在空投和其他批量转移中。

1 对多转移

多对 1 转移很少见。这些转移的用例通常是在多个帐户之间合并资金或进行复利

多对 1 转移

最后,多对多转移是模棱两可的。通常,你应用一个规则,例如先进先出 (FIFO) 或后进先出 (LIFO),使用事件索引进行排序,以获得确定性的单式记账。多对多转移通常发生在 DeFi 聚合器交易中,因为它们会将资金分配到不同的去中心化交易所,以找到最佳汇率。

多对多转移

假设你已经有一个 SQL 表,该表可以解析出 mint/burn/withdraw/deposit,那么你可以使用以下逻辑构建单式记账表(不包括多对多)

WITH cumulative_bal AS (
    -- if no event_index mint/burn, assign low number for mint and high number for burn
    SELECT *,
        SUM(
    CASE -- start with moving money out of store
        WHEN activity_type = 'Withdraw' THEN -amount
        WHEN activity_type = 'Mint' THEN -amount
        ELSE amount -- Deposit or Burn
    END
 ) OVER (PARTITION BY tx_version ORDER BY event_index ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance_tracker
    FROM events_parsed
), sessioning AS (
    -- balance_tracker starts negative, when it gets to 0 start new session
    SELECT *,
        COALESCE(SUM(IF(balance_tracker >= 0, 1, 0)) OVER (PARTITION BY tx_version ORDER BY event_index ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),0) AS session_id,
        balance_tracker > 0 AS missing_event, -- qc
    FROM cumulative_bal
), session_sum AS (
    SELECT *,
        SUM(amount) OVER (
            PARTITION BY tx_version, session_id, IF(activity_type IN ('Withdraw', 'Mint'), 1, 0)
            ORDER BY event_index ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS amount_csum,
        SUM(amount) OVER (
            PARTITION BY tx_version, session_id, IF(activity_type IN ('Withdraw', 'Mint'), 1, 0)
            ORDER BY event_index ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
        ) AS amount_csum_prev,
    FROM sessioning
), session_counter AS (
    SELECT
        tx_version,
        session_id,
        COUNT(1) AS n_events,
        SUM(IF(activity_type = 'Withdraw', 1, 0)) AS n_withdraw,
        SUM(IF(activity_type = 'Deposit', 1, 0)) AS n_deposit,
        SUM(IF(activity_type = 'Mint', 1, 0)) AS n_mint,
        SUM(IF(activity_type = 'Burn', 1, 0)) AS n_burn,
        MAX(amount) AS amount_max,
        MAX(IF(activity_type = 'Withdraw', amount, 0)) AS max_withdraw,
        MAX(IF(activity_type = 'Deposit', amount, 0)) AS max_deposit,
        MAX(IF(activity_type = 'Mint', amount, 0)) AS max_mint,
        MAX(IF(activity_type = 'Burn', amount, 0)) AS max_burn,
    FROM sessioning
    GROUP BY ALL
), transfers_ez AS (
    -- cannot handle many:many
    SELECT
        w.block_timestamp,
        w.tx_version,
        w.tx_hash,
        w.session_id,
        w.store_owner AS from_account,
        w.fungible_store AS from_store,
        d.store_owner AS to_account,
        d.fungible_store AS to_store,
        w.amount AS from_amount,
        d.amount AS to_amount,
        LEAST(w.amount, d.amount) AS amount,
        w.event_index AS from_index,
        d.event_index AS to_index,
        sc.n_withdraw + sc.n_mint > 1 AS from_many,
        sc.n_deposit + sc.n_burn > 1 AS to_many,
        sc.n_mint > 0 AS has_mint,
        sc.n_burn > 0 AS has_burn,
    FROM sessioning w
    INNER JOIN sessioning d
    ON w.tx_version = d.tx_version
    AND w.event_index < d.event_index
    AND (w.activity_type = 'Withdraw' OR w.activity_type = 'Mint')
    AND (d.activity_type = 'Deposit' OR d.activity_type = 'Burn')
    AND w.session_id = d.session_id
    LEFT JOIN session_counter sc
    ON w.tx_version = sc.tx_version
    AND w.session_id = sc.session_id
    WHERE 1=1
    AND (n_withdraw + n_mint = 1 OR n_deposit + n_burn = 1) -- exclude many:many
    -- AND w.fungible_store != d.fungible_store -- remove self transfers
)

SELECT *, ROW_NUMBER() OVER (PARTITION BY tx_version ORDER BY amount DESC) AS amount_rank
FROM transfers_ez

虽然可以使用 SQL 中的 FIFO 来实现多对多,但我已将该练习留给读者。

特殊情况

APT mints 和 burns

虽然可以通过每天调用 supply 视图函数来计算 APT 的有效通货膨胀率,但在两种情况下 APT 供应量会增加,在一种情况下 APT 供应量会减少。

APT 不断被铸造用于质押奖励,并在 epoch 结束时支付(主网为 2 小时)。由于它是铸造到自定义的 coin 持有者,因此不会计入交易量。

可以使用以下方法找到历史奖励率奖励率

SELECT
    tx_version,
    block_timestamp,
    type_str,
    -- resource,
    COALESCE(
        -- pct increase per epoch
        CAST(JSON_EXTRACT_SCALAR(resource, '$.rewards_rate.value') AS INT64)/POW(2,64), -- StakingRewardsConfig
        CAST(JSON_EXTRACT_SCALAR(resource, '$.rewards_rate') AS INT64) / CAST(JSON_EXTRACT_SCALAR(resource, '$.rewards_rate_denominator') AS INT64) -- StakingConfig
    ) AS reward_rate_per_epoch,
FROM `bigquery-public-data.crypto_aptos_mainnet_us.resources` r
WHERE 1=1
AND (type_str = '0x1::staking_config::StakingConfig' AND tx_version < 1710875717)
OR (type_str = '0x1::staking_config::StakingRewardsConfig')
ORDER BY tx_version

当前的费率约为 1.55052e-05 / epoch,约为 6.796 APR (1.0000155051.. * 12 * 365)

此奖励率每年都会降低,这是通过 AIP-30 实现的

作为一种特殊情况,gas 费用 没有 withdrawal 或 deposit(在净退款的情况下)事件。相反,每个交易都有一个 0x1::transaction_fee::FeeStatement 事件,该事件列出了以 octa 为单位的执行和存储成本。大量删除的交易将是 APT 净铸造。每个交易都会燃烧 APT 用于执行。

移除 mint 能力

可以销毁 mint 能力,以确保供应量不再增加。

具有 fungible assets 的数字资产

由于数字资产可以持有 fungible assets,因此可以使用数字资产来表示子钱包。例如,Pact Labs 使用数字资产来表示贷款(每个集合都是一个贷款组合),并且每个数字资产都在贷款还清时被销毁(ex)。

移除非自然交易量

交易量可能会因来回发送资产或在帐户环中发送资产而膨胀。标记非自然交易量的两种广泛方法是查看发送者的交易数量或发送者在一段时间内的交易量。例如,如果在过去一周内,少数未知地址占某个资产交易量的 80% 以上,那么可能应该删除这些地址。这就是标记可能非常重要的地方,因为高交易量地址可能属于(去)中心化交易所。虽然阈值可能会因用例而异,但 Allium 有一些关于他们用于过滤稳定币的指南

关于流通供应量的想法

为了获得流通供应量,我们可以获取总供应量并移除属于以下情况的资产

  • 丢失的资金(地址中没有所有者或从未执行过交易的资产)
  • 冻结在 CoinStore/FungibleStore 中
  • 链上锁定(例如,质押的 APT)可以通过编程方式进行验证
  • 链下锁定(例如,未归属的代币)只能跟踪钱包没有活动
  • 有义务锁定 / 不转移(例如,储备)与上述类似

链下义务是不可能列出的,但一个例子是 0xd5b71ee4d1bad5cb7f14c880ee55633c7befcb7384cf070919ea5c481019a4e9 拥有的 USDt 是 储备 的一部分,因此任何转出的转账都可以被视为 mint,并且市值计算应排除此地址持有的资产。

结论

Coins 和 Fungible Assets 是在链上表示资产的强大原语。引入 Fungible Assets 是为了解决跟踪 Coins 的复杂性。建议所有新合约都编写为 FA(因为 Coins 可以自动迁移),并且新资产部署为原生 FA。

在接下来的几周内,全网迁移 将开始推出,以将所有 coins 过渡到 FA。在此迁移之后,在 Aptos 上使用资产应该会变得更加简单。

  • 原文链接: aptoslabs.medium.com/dat...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
aptoslabs
aptoslabs
江湖只有他的大名,没有他的介绍。