gcx/blockchain/explorer/coupon-view.ex

156 lines
4.8 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

defmodule BlockScoutWeb.CouponView do
@moduledoc """
Blockscout 定制模块券NFT详情页扩展
解析 CouponFactory / Coupon 合约事件,展示券业务字段
功能清单 (P0):
- 面值 (face_value)
- 券类型 (Utility / Security)
- 到期日 (expiry_date)
- 转售计数 (resale_count / max_resale)
- 发行方 (issuer)
- 可转让性 (transferable)
"""
alias Explorer.Chain.{Token, TokenTransfer}
alias Explorer.SmartContract.Reader
# CouponBatchMinted 事件签名
@coupon_batch_minted_topic "0x" <>
Base.encode16(:crypto.hash(:keccak256, "CouponBatchMinted(uint256,address,uint8,uint256,uint256,uint256)"), case: :lower)
# Coupon 合约 ABI 片段(只读函数)
@coupon_abi [
%{
"name" => "getConfig",
"type" => "function",
"stateMutability" => "view",
"inputs" => [%{"name" => "tokenId", "type" => "uint256"}],
"outputs" => [
%{"name" => "issuer", "type" => "address"},
%{"name" => "faceValue", "type" => "uint256"},
%{"name" => "couponType", "type" => "uint8"},
%{"name" => "expiryDate", "type" => "uint256"},
%{"name" => "maxResaleCount", "type" => "uint256"},
%{"name" => "transferable", "type" => "bool"}
]
},
%{
"name" => "getResaleCount",
"type" => "function",
"stateMutability" => "view",
"inputs" => [%{"name" => "tokenId", "type" => "uint256"}],
"outputs" => [%{"name" => "", "type" => "uint256"}]
}
]
@doc "渲染券NFT详情页数据"
def render_coupon_detail(token_id, contract_address) do
with {:ok, config} <- call_get_config(contract_address, token_id),
{:ok, resale_count} <- call_get_resale_count(contract_address, token_id) do
%{
token_id: token_id,
face_value: config.face_value,
coupon_type: decode_coupon_type(config.coupon_type),
expiry_date: DateTime.from_unix!(config.expiry_date),
max_resale_count: config.max_resale_count,
resale_count: resale_count,
transferable: config.transferable,
issuer: config.issuer,
expired: DateTime.utc_now() > DateTime.from_unix!(config.expiry_date)
}
else
{:error, reason} -> {:error, reason}
end
end
@doc "解析 CouponBatchMinted 事件日志"
def parse_batch_minted_event(log) do
case log.first_topic do
@coupon_batch_minted_topic ->
%{
batch_id: decode_uint256(log.second_topic),
issuer: decode_address(log.third_topic),
coupon_type: decode_coupon_type(decode_uint8_from_data(log.data, 0)),
face_value: decode_uint256_from_data(log.data, 1),
quantity: decode_uint256_from_data(log.data, 2),
start_token_id: decode_uint256_from_data(log.data, 3)
}
_ ->
nil
end
end
@doc "获取券批次摘要信息"
def get_batch_summary(batch_id, contract_address) do
%{
batch_id: batch_id,
contract: contract_address,
minted_event: find_batch_minted_event(batch_id, contract_address),
holder_count: count_current_holders(batch_id, contract_address)
}
end
# ── 私有函数 ──
defp call_get_config(contract_address, token_id) do
case Reader.query_contract(contract_address, @coupon_abi, %{
"getConfig" => [token_id]
}) do
%{"getConfig" => {:ok, [issuer, face_value, coupon_type, expiry_date, max_resale, transferable]}} ->
{:ok,
%{
issuer: issuer,
face_value: face_value,
coupon_type: coupon_type,
expiry_date: expiry_date,
max_resale_count: max_resale,
transferable: transferable
}}
_ ->
{:error, :contract_call_failed}
end
end
defp call_get_resale_count(contract_address, token_id) do
case Reader.query_contract(contract_address, @coupon_abi, %{
"getResaleCount" => [token_id]
}) do
%{"getResaleCount" => {:ok, [count]}} -> {:ok, count}
_ -> {:error, :contract_call_failed}
end
end
defp decode_coupon_type(0), do: :utility
defp decode_coupon_type(1), do: :security
defp decode_coupon_type(_), do: :unknown
defp decode_uint256(nil), do: 0
defp decode_uint256("0x" <> hex), do: String.to_integer(hex, 16)
defp decode_address(nil), do: nil
defp decode_address("0x000000000000000000000000" <> addr), do: "0x" <> addr
defp decode_uint256_from_data(data, offset) do
data
|> String.slice(2 + offset * 64, 64)
|> String.to_integer(16)
end
defp decode_uint8_from_data(data, offset) do
decode_uint256_from_data(data, offset)
end
defp find_batch_minted_event(_batch_id, _contract_address) do
# 查询链上日志获取铸造事件
nil
end
defp count_current_holders(_batch_id, _contract_address) do
# 聚合当前持有人数量
0
end
end