Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/subgraph/config/Payments-abi.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
[
{
"type": "function",
"name": "burnForFees",
"inputs": [
{ "name": "token", "type": "address", "internalType": "contract IERC20" },
{ "name": "recipient", "type": "address", "internalType": "address" },
{ "name": "requested", "type": "uint256", "internalType": "uint256" }
],
"outputs": [],
"stateMutability": "payable"
},
{
"type": "event",
"name": "AccountLockupSettled",
Expand Down
17 changes: 17 additions & 0 deletions packages/subgraph/schemas/schema.v1.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type Token @entity(immutable: false) {
totalUsers: BigInt!
userFunds: BigInt! # same as sum of all UserToken.funds
operatorCommission: BigInt!
accumulatedFees: BigInt! # ERC-20 fees pending in dutch auction
totalFilBurnedForFees: BigInt! # Total FIL burned purchasing this token's fees
lockupCurrent: BigInt!
lockupRate: BigInt!
lockupLastSettledUntilEpoch: BigInt!
Expand Down Expand Up @@ -246,6 +248,10 @@ type DailyTokenMetric @entity(immutable: false) {
activeRailsCount: BigInt!
uniqueHolders: BigInt!
totalLocked: BigInt!

# Auction stats
accumulatedFees: BigInt!
totalFilBurnedForFees: BigInt!
}

type DailyOperatorMetric @entity(immutable: false) {
Expand All @@ -261,3 +267,14 @@ type DailyOperatorMetric @entity(immutable: false) {
uniqueClients: BigInt!
totalApprovals: BigInt!
}

type FeeAuctionPurchase @entity(immutable: true) {
id: Bytes! # txHash + txIndex
token: Token!
recipient: Bytes!
amountPurchased: BigInt! # ERC-20 tokens purchased
filBurned: BigInt! # FIL burned to purchase
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
59 changes: 50 additions & 9 deletions packages/subgraph/src/payments.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Address, Bytes, log } from "@graphprotocol/graph-ts";
import {
AccountLockupSettled as AccountLockupSettledEvent,
BurnForFeesCall,
DepositRecorded as DepositRecordedEvent,
OperatorApprovalUpdated as OperatorApprovalUpdatedEvent,
RailCreated as RailCreatedEvent,
Expand All @@ -12,7 +13,7 @@ import {
RailTerminated as RailTerminatedEvent,
WithdrawRecorded as WithdrawRecordedEvent,
} from "../generated/Payments/Payments";
import { Account, OperatorApproval, Rail, Settlement, Token, UserToken } from "../generated/schema";
import { Account, FeeAuctionPurchase, OperatorApproval, Rail, Settlement, Token, UserToken } from "../generated/schema";
import {
computeSettledLockup,
createOneTimePayment,
Expand All @@ -25,14 +26,20 @@ import {
epochsRateChangeApplicable,
getLockupLastSettledUntilTimestamp,
getTokenDetails,
isNativeToken,
remainingEpochsForTerminatedRail,
updateOperatorLockup,
updateOperatorRate,
updateOperatorTokenLockup,
updateOperatorTokenRate,
} from "./utils/helpers";
import { getIdFromTxHashAndLogIndex, getRailEntityId, getSettlementEntityId } from "./utils/keys";
import { MetricsCollectionOrchestrator, ONE_BIG_INT, ZERO_BIG_INT } from "./utils/metrics";
import {
getFeeAuctionPurchaseEntityId,
getIdFromTxHashAndLogIndex,
getRailEntityId,
getSettlementEntityId,
} from "./utils/keys";
import { MetricsCollectionOrchestrator, MetricsEntityManager, ONE_BIG_INT, ZERO_BIG_INT } from "./utils/metrics";

export function handleAccountLockupSettled(event: AccountLockupSettledEvent): void {
const tokenAddress = event.params.token;
Expand Down Expand Up @@ -449,15 +456,14 @@ export function handleRailSettled(event: RailSettledEvent): void {
token.lockupLastSettledUntilEpoch = event.block.number;

// Subtract the network fee from user funds since it is not retained by the user.
// The fee is either burned or deposited into the Filecoin-pay contract account.
//
// NOTE: When the auction system is integrated, the network fee will be
// deposited into the Filecoin-pay contract. At that point, we should stop
// subtracting the network fee here, as `token.userFunds` can be derived
// by summing all `userTokens.funds`.
token.userFunds = token.userFunds.minus(networkFee);
token.totalSettledAmount = token.totalSettledAmount.plus(totalSettledAmount);

// For ERC-20 tokens, the network fee accumulates for dutch auction
// For native FIL, the fee is burned directly (no accumulated fees to track)
if (!isNativeToken(rail.token)) {
token.accumulatedFees = token.accumulatedFees.plus(networkFee);
}
// Reduce streaming lockup by rate × actualSettledDuration.
// Settlement window is (previousSettledUpto, settledUpTo].
// RateChangeQueue applies for (startEpoch, untilEpoch], i.e., startEpoch is exclusive.
Expand Down Expand Up @@ -628,6 +634,12 @@ export function handleRailOneTimePaymentProcessed(event: RailOneTimePaymentProce
token.userFunds = token.userFunds.minus(networkFee);
token.lockupCurrent = token.lockupCurrent.minus(totalAmount);
token.totalOneTimePayment = token.totalOneTimePayment.plus(totalAmount);

// For ERC-20 tokens, the network fee accumulates for dutch auction
// For native FIL, the fee is burned directly (no accumulated fees to track)
if (!isNativeToken(rail.token)) {
token.accumulatedFees = token.accumulatedFees.plus(networkFee);
}
token.save();
}
if (payerToken) {
Expand Down Expand Up @@ -710,3 +722,32 @@ export function handleRailFinalized(event: RailFinalizedEvent): void {
event.block.number,
);
}

// ==================== Call Handlers ====================

export function handleBurnForFees(call: BurnForFeesCall): void {
const tokenAddress = call.inputs.token;
const recipient = call.inputs.recipient;
const requested = call.inputs.requested;
const filBurned = call.transaction.value;

// Create FeeAuctionPurchase entity
const purchaseId = getFeeAuctionPurchaseEntityId(call.transaction.hash, call.transaction.index);
const purchase = new FeeAuctionPurchase(purchaseId);
purchase.token = tokenAddress;
purchase.recipient = recipient;
purchase.amountPurchased = requested;
purchase.filBurned = filBurned;
purchase.blockNumber = call.block.number;
purchase.blockTimestamp = call.block.timestamp;
purchase.transactionHash = call.transaction.hash;
purchase.save();

MetricsCollectionOrchestrator.collectFeeAuctionMetrics(
requested,
filBurned,
tokenAddress,
call.block.timestamp,
call.block.number,
);
}
7 changes: 7 additions & 0 deletions packages/subgraph/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import {
} from "./keys";
import { ZERO_BIG_INT } from "./metrics";

// Checks if token is native FIL (address zero)
export function isNativeToken(tokenAddress: Bytes): boolean {
return Address.fromBytes(tokenAddress).equals(Address.zero());
}

class TokenDetails {
constructor(
public token: Token,
Expand Down Expand Up @@ -124,6 +129,8 @@ export const getTokenDetails = (address: Address): TokenDetails => {
token.lockupRate = ZERO_BIG_INT;
token.lockupLastSettledUntilEpoch = ZERO_BIG_INT;
token.totalUsers = ZERO_BIG_INT;
token.accumulatedFees = ZERO_BIG_INT;
token.totalFilBurnedForFees = ZERO_BIG_INT;

return new TokenDetails(token, true);
}
Expand Down
4 changes: 4 additions & 0 deletions packages/subgraph/src/utils/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ export function getSettlementEntityId(txHash: Bytes, logIndex: BigInt): Bytes {
export function getPaymentsMetricEntityId(): Bytes {
return Bytes.fromUTF8(PAYMENTS_NETWORK_STATS_ID);
}

export function getFeeAuctionPurchaseEntityId(txHash: Bytes, txIndex: BigInt): Bytes {
return txHash.concatI32(txIndex.toI32());
}
105 changes: 101 additions & 4 deletions packages/subgraph/src/utils/metrics/collectors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Address, Bytes, BigInt as GraphBN } from "@graphprotocol/graph-ts";
import { Rail } from "../../../generated/schema";
import { isNativeToken } from "../helpers";
import { ONE_BIG_INT, ZERO_BIG_INT } from "./constants";
import { MetricsEntityManager } from "./core";

Expand Down Expand Up @@ -129,6 +130,7 @@ export class SettlementCollector extends BaseMetricsCollector {
private totalSettledAmount: GraphBN;
private operatorCommission: GraphBN;
private networkFee: GraphBN;
private isNativeFil: boolean;

constructor(
rail: Rail,
Expand All @@ -143,6 +145,7 @@ export class SettlementCollector extends BaseMetricsCollector {
this.totalSettledAmount = totalSettledAmount;
this.operatorCommission = operatorCommission;
this.networkFee = networkFee;
this.isNativeFil = isNativeToken(rail.token);
}

collect(): void {
Expand All @@ -156,13 +159,19 @@ export class SettlementCollector extends BaseMetricsCollector {
// Daily metrics
const dailyMetric = MetricsEntityManager.loadOrCreateDailyMetric(this.timestamp);
dailyMetric.totalRailSettlements = dailyMetric.totalRailSettlements.plus(ONE_BIG_INT);
dailyMetric.filBurned = dailyMetric.filBurned.plus(this.networkFee);
// Only add to filBurned if token is native FIL
if (this.isNativeFil) {
dailyMetric.filBurned = dailyMetric.filBurned.plus(this.networkFee);
}
dailyMetric.save();

// Weekly metrics
const weeklyMetric = MetricsEntityManager.loadOrCreateWeeklyMetric(this.timestamp);
weeklyMetric.totalRailSettlements = weeklyMetric.totalRailSettlements.plus(ONE_BIG_INT);
weeklyMetric.filBurned = weeklyMetric.filBurned.plus(this.networkFee);
// Only add to filBurned if token is native FIL
if (this.isNativeFil) {
weeklyMetric.filBurned = weeklyMetric.filBurned.plus(this.networkFee);
}
weeklyMetric.save();
}

Expand Down Expand Up @@ -193,7 +202,10 @@ export class SettlementCollector extends BaseMetricsCollector {
private updateNetworkMetrics(): void {
const networkMetric = MetricsEntityManager.loadOrCreatePaymentsMetric();

networkMetric.totalFilBurned = networkMetric.totalFilBurned.plus(this.networkFee);
// Only add to filBurned if token is native FIL
if (this.isNativeFil) {
networkMetric.totalFilBurned = networkMetric.totalFilBurned.plus(this.networkFee);
}
networkMetric.totalRailSettlements = networkMetric.totalRailSettlements.plus(ONE_BIG_INT);

networkMetric.save();
Expand Down Expand Up @@ -382,22 +394,30 @@ export class OneTimePaymentCollector extends BaseMetricsCollector {
private totalAmount: GraphBN;
private networkFee: GraphBN;
private token: Bytes;
private isNativeFil: boolean;

constructor(totalAmount: GraphBN, networkFee: GraphBN, token: Bytes, timestamp: GraphBN, blockNumber: GraphBN) {
super(timestamp, blockNumber);
this.totalAmount = totalAmount;
this.networkFee = networkFee;
this.token = token;
this.isNativeFil = isNativeToken(token);
}

collect(): void {
this.updateNetworkMetrics();
this.updateTokenMetrics();
this.updateDailyMetrics();
this.updateWeeklyMetrics();
}

private updateNetworkMetrics(): void {
const networkMetric = MetricsEntityManager.loadOrCreatePaymentsMetric();
networkMetric.totalFilBurned = networkMetric.totalFilBurned.plus(this.networkFee);
// Only add to filBurned if token is native FIL
if (this.isNativeFil) {
networkMetric.totalFilBurned = networkMetric.totalFilBurned.plus(this.networkFee);
}
networkMetric.save();
}

private updateTokenMetrics(): void {
Expand All @@ -408,6 +428,72 @@ export class OneTimePaymentCollector extends BaseMetricsCollector {

tokenMetric.save();
}

private updateDailyMetrics(): void {
const dailyMetric = MetricsEntityManager.loadOrCreateDailyMetric(this.timestamp);
if (this.isNativeFil) {
dailyMetric.filBurned = dailyMetric.filBurned.plus(this.networkFee);
}
dailyMetric.save();
}

private updateWeeklyMetrics(): void {
const weeklyMetric = MetricsEntityManager.loadOrCreateWeeklyMetric(this.timestamp);
if (this.isNativeFil) {
weeklyMetric.filBurned = weeklyMetric.filBurned.plus(this.networkFee);
}
weeklyMetric.save();
}
}

// One Time Payment Collector
export class FeeAuctionCollector extends BaseMetricsCollector {
private amountPurchased: GraphBN;
private filBurned: GraphBN;
private token: Bytes;

constructor(amountPurchased: GraphBN, filBurned: GraphBN, token: Bytes, timestamp: GraphBN, blockNumber: GraphBN) {
super(timestamp, blockNumber);
this.amountPurchased = amountPurchased;
this.filBurned = filBurned;
this.token = token;
}

collect(): void {
this.updateNetworkMetrics();
this.updateTokenMetrics();
this.updateDailyMetrics();
this.updateWeeklyMetrics();
}

private updateNetworkMetrics(): void {
const networkMetric = MetricsEntityManager.loadOrCreatePaymentsMetric();

networkMetric.totalFilBurned = networkMetric.totalFilBurned.plus(this.filBurned);
networkMetric.save();
}

private updateTokenMetrics(): void {
const tokenMetric = MetricsEntityManager.loadOrCreateTokenMetric(Address.fromBytes(this.token), this.timestamp);

tokenMetric.accumulatedFees = tokenMetric.accumulatedFees.minus(this.amountPurchased);
tokenMetric.totalFilBurnedForFees = tokenMetric.totalFilBurnedForFees.plus(this.filBurned);
tokenMetric.save();
}

private updateDailyMetrics(): void {
const dailyMetric = MetricsEntityManager.loadOrCreateDailyMetric(this.timestamp);

dailyMetric.filBurned = dailyMetric.filBurned.plus(this.filBurned);
dailyMetric.save();
}

private updateWeeklyMetrics(): void {
const weeklyMetric = MetricsEntityManager.loadOrCreateWeeklyMetric(this.timestamp);

weeklyMetric.filBurned = weeklyMetric.filBurned.plus(this.filBurned);
weeklyMetric.save();
}
}

// Metrics Collection Orchestrator
Expand Down Expand Up @@ -510,4 +596,15 @@ export class MetricsCollectionOrchestrator {
const collector = new OneTimePaymentCollector(totalAmount, networkFee, token, timestamp, blockNumber);
collector.collect();
}

static collectFeeAuctionMetrics(
amountPurchased: GraphBN,
filBurned: GraphBN,
token: Bytes,
timestamp: GraphBN,
blockNumber: GraphBN,
): void {
const collector = new FeeAuctionCollector(amountPurchased, filBurned, token, timestamp, blockNumber);
collector.collect();
}
}
4 changes: 4 additions & 0 deletions packages/subgraph/templates/subgraph.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dataSources:
- Account
- Operator
- Payments
- FeeAuctionPurchase
abis:
- name: Payments
file: ./config/Payments-abi.json
Expand Down Expand Up @@ -57,4 +58,7 @@ dataSources:
handler: handleWithdrawRecorded
- event: AccountLockupSettled(indexed address,indexed address,uint256,uint256,uint256)
handler: handleAccountLockupSettled
callHandlers:
- function: burnForFees(address,address,uint256)
handler: handleBurnForFees
file: ./src/payments.ts
Loading