From c9291d043ce1604d6af94ebb888a5c814513038b Mon Sep 17 00:00:00 2001 From: Kai Brusch Date: Wed, 30 Jul 2025 12:17:09 -0400 Subject: [PATCH 1/5] Add fee tracking subgraph draft - Add schema with FeeEvent, GlobalFeeStats, and UserFeeStats entities - Add subgraph manifest template for FeeModule contract - Add mapping file to handle FeeCharged events - Add placeholder ABI for FeeModule contract - Add README with usage examples This subgraph will track fee events to monitor the difference between exchange fees and schedule fees, ensuring users only pay intended amounts. --- fee-tracking-subgraph/README.md | 92 +++++++++++++++++++ fee-tracking-subgraph/abis/FeeModule.json | 39 ++++++++ fee-tracking-subgraph/schema.graphql | 52 +++++++++++ fee-tracking-subgraph/src/FeeModuleMapping.ts | 73 +++++++++++++++ fee-tracking-subgraph/subgraph.template.yaml | 26 ++++++ 5 files changed, 282 insertions(+) create mode 100644 fee-tracking-subgraph/README.md create mode 100644 fee-tracking-subgraph/abis/FeeModule.json create mode 100644 fee-tracking-subgraph/schema.graphql create mode 100644 fee-tracking-subgraph/src/FeeModuleMapping.ts create mode 100644 fee-tracking-subgraph/subgraph.template.yaml diff --git a/fee-tracking-subgraph/README.md b/fee-tracking-subgraph/README.md new file mode 100644 index 0000000..272a4f0 --- /dev/null +++ b/fee-tracking-subgraph/README.md @@ -0,0 +1,92 @@ +# Fee Tracking Subgraph + +This subgraph tracks fee events from the Polymarket FeeModule contract, monitoring the difference between exchange fees and schedule fees, as well as refunds to ensure users only pay the intended fee amount. + +## Purpose + +The fee tracking subgraph monitors: +- Exchange fees charged by the underlying exchange +- Schedule fees calculated by our fee formula +- Refunds given to users +- Net fees actually paid by users + +This helps ensure the fee system works as intended and provides transparency into fee collection. + +## Entities + +### FeeEvent +Tracks individual fee events with: +- User who paid the fee +- Exchange fee amount +- Schedule fee amount +- Refund amount +- Net fee amount +- Timestamp and transaction details + +### GlobalFeeStats +Aggregate statistics across all fee events: +- Total exchange fees charged +- Total schedule fees +- Total refunds given +- Total net fees collected +- Total number of fee events + +### UserFeeStats +Per-user fee statistics: +- User's total exchange fees paid +- User's total schedule fees +- User's total refunds received +- User's total net fees paid +- User's fee event count and last event timestamp + +## Usage + +Query fee events: +```graphql +{ + feeEvents(first: 10, orderBy: timestamp, orderDirection: desc) { + user + exchangeFee + scheduleFee + refundAmount + netFee + timestamp + } +} +``` + +Query global statistics: +```graphql +{ + globalFeeStats(id: "") { + totalExchangeFees + totalScheduleFees + totalRefunds + totalNetFees + feeEventCount + } +} +``` + +Query user statistics: +```graphql +{ + userFeeStats(id: "0xUserAddress") { + totalExchangeFees + totalScheduleFees + totalRefunds + totalNetFees + feeEventCount + lastFeeEvent + } +} +``` + +## Deployment + +This subgraph requires: +- FeeModule contract address +- Start block for indexing +- Network configuration + +Update the `subgraph.template.yaml` with the correct contract address and start block before deployment. \ No newline at end of file diff --git a/fee-tracking-subgraph/abis/FeeModule.json b/fee-tracking-subgraph/abis/FeeModule.json new file mode 100644 index 0000000..f21e547 --- /dev/null +++ b/fee-tracking-subgraph/abis/FeeModule.json @@ -0,0 +1,39 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exchangeFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "scheduleFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refundAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "netFee", + "type": "uint256" + } + ], + "name": "FeeCharged", + "type": "event" + } +] \ No newline at end of file diff --git a/fee-tracking-subgraph/schema.graphql b/fee-tracking-subgraph/schema.graphql new file mode 100644 index 0000000..eae9d7f --- /dev/null +++ b/fee-tracking-subgraph/schema.graphql @@ -0,0 +1,52 @@ +type FeeEvent @entity { + "Transaction hash + log index" + id: ID! + "User who paid the fee" + user: String! + "Fee charged by the exchange" + exchangeFee: BigInt! + "Fee according to our fee schedule" + scheduleFee: BigInt! + "Amount refunded to user" + refundAmount: BigInt! + "Net fee actually paid by user" + netFee: BigInt! + "Timestamp of the fee event" + timestamp: BigInt! + "Transaction hash" + txHash: String! + "Block number" + blockNumber: BigInt! +} + +type GlobalFeeStats @entity { + "Empty string" + id: ID! + "Total exchange fees charged" + totalExchangeFees: BigInt! + "Total schedule fees" + totalScheduleFees: BigInt! + "Total refunds given" + totalRefunds: BigInt! + "Total net fees collected" + totalNetFees: BigInt! + "Number of fee events" + feeEventCount: BigInt! +} + +type UserFeeStats @entity { + "User address" + id: ID! + "Total exchange fees paid by user" + totalExchangeFees: BigInt! + "Total schedule fees for user" + totalScheduleFees: BigInt! + "Total refunds received by user" + totalRefunds: BigInt! + "Total net fees paid by user" + totalNetFees: BigInt! + "Number of fee events for user" + feeEventCount: BigInt! + "Last fee event timestamp" + lastFeeEvent: BigInt! +} \ No newline at end of file diff --git a/fee-tracking-subgraph/src/FeeModuleMapping.ts b/fee-tracking-subgraph/src/FeeModuleMapping.ts new file mode 100644 index 0000000..113bf09 --- /dev/null +++ b/fee-tracking-subgraph/src/FeeModuleMapping.ts @@ -0,0 +1,73 @@ +import { BigInt } from '@graphprotocol/graph-ts'; +import { FeeCharged } from '../types/FeeModule/FeeModule'; +import { FeeEvent, GlobalFeeStats, UserFeeStats } from '../types/schema'; + +function getGlobalFeeStats(): GlobalFeeStats { + let globalStats = GlobalFeeStats.load(''); + if (globalStats == null) { + globalStats = new GlobalFeeStats(''); + globalStats.totalExchangeFees = BigInt.fromI32(0); + globalStats.totalScheduleFees = BigInt.fromI32(0); + globalStats.totalRefunds = BigInt.fromI32(0); + globalStats.totalNetFees = BigInt.fromI32(0); + globalStats.feeEventCount = BigInt.fromI32(0); + } + return globalStats; +} + +function getUserFeeStats(userAddress: string): UserFeeStats { + let userStats = UserFeeStats.load(userAddress); + if (userStats == null) { + userStats = new UserFeeStats(userAddress); + userStats.totalExchangeFees = BigInt.fromI32(0); + userStats.totalScheduleFees = BigInt.fromI32(0); + userStats.totalRefunds = BigInt.fromI32(0); + userStats.totalNetFees = BigInt.fromI32(0); + userStats.feeEventCount = BigInt.fromI32(0); + userStats.lastFeeEvent = BigInt.fromI32(0); + } + return userStats; +} + +export function handleFeeCharged(event: FeeCharged): void { + // Extract event parameters + const user = event.params.user.toHexString(); + const exchangeFee = event.params.exchangeFee; + const scheduleFee = event.params.scheduleFee; + const refundAmount = event.params.refundAmount; + const netFee = event.params.netFee; + + // Create unique ID for the fee event + const eventId = event.transaction.hash.toHexString() + '-' + event.logIndex.toString(); + + // Create FeeEvent entity + const feeEvent = new FeeEvent(eventId); + feeEvent.user = user; + feeEvent.exchangeFee = exchangeFee; + feeEvent.scheduleFee = scheduleFee; + feeEvent.refundAmount = refundAmount; + feeEvent.netFee = netFee; + feeEvent.timestamp = event.block.timestamp; + feeEvent.txHash = event.transaction.hash.toHexString(); + feeEvent.blockNumber = event.block.number; + feeEvent.save(); + + // Update global fee statistics + const globalStats = getGlobalFeeStats(); + globalStats.totalExchangeFees = globalStats.totalExchangeFees.plus(exchangeFee); + globalStats.totalScheduleFees = globalStats.totalScheduleFees.plus(scheduleFee); + globalStats.totalRefunds = globalStats.totalRefunds.plus(refundAmount); + globalStats.totalNetFees = globalStats.totalNetFees.plus(netFee); + globalStats.feeEventCount = globalStats.feeEventCount.plus(BigInt.fromI32(1)); + globalStats.save(); + + // Update user fee statistics + const userStats = getUserFeeStats(user); + userStats.totalExchangeFees = userStats.totalExchangeFees.plus(exchangeFee); + userStats.totalScheduleFees = userStats.totalScheduleFees.plus(scheduleFee); + userStats.totalRefunds = userStats.totalRefunds.plus(refundAmount); + userStats.totalNetFees = userStats.totalNetFees.plus(netFee); + userStats.feeEventCount = userStats.feeEventCount.plus(BigInt.fromI32(1)); + userStats.lastFeeEvent = event.block.timestamp; + userStats.save(); +} \ No newline at end of file diff --git a/fee-tracking-subgraph/subgraph.template.yaml b/fee-tracking-subgraph/subgraph.template.yaml new file mode 100644 index 0000000..7f04914 --- /dev/null +++ b/fee-tracking-subgraph/subgraph.template.yaml @@ -0,0 +1,26 @@ +specVersion: 0.0.4 +schema: + file: ./schema.graphql +dataSources: + - kind: ethereum + name: FeeModule + network: {{ network }} + source: + address: "{{ contracts.FeeModule.address }}" + abi: FeeModule + startBlock: {{ contracts.FeeModule.startBlock }} + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - FeeEvent + - GlobalFeeStats + - UserFeeStats + abis: + - name: FeeModule + file: ./abis/FeeModule.json + eventHandlers: + - event: FeeCharged(indexed address,uint256,uint256,uint256,uint256) + handler: handleFeeCharged + file: ./src/FeeModuleMapping.ts \ No newline at end of file From bcc4dd75faeabe6882a9cc6ca2f215df323c5276 Mon Sep 17 00:00:00 2001 From: Kai Brusch Date: Wed, 30 Jul 2025 13:25:40 -0400 Subject: [PATCH 2/5] Update fee tracking subgraph to match actual FeeModule contract - Update ABI with actual events: FeeRefunded and FeeWithdrawn - Update schema to match actual event parameters - Update mapping to handle FeeRefunded and FeeWithdrawn events - Update subgraph manifest with correct event handlers - Update README with correct entity descriptions and queries The subgraph now tracks: - Fee refunds given to users when actual fees exceed intended fees - Fee withdrawals by administrators - Per-user and global fee statistics --- fee-tracking-subgraph/README.md | 88 +++++++++------ fee-tracking-subgraph/abis/FeeModule.json | 43 ++++++- fee-tracking-subgraph/schema.graphql | 75 ++++++++----- fee-tracking-subgraph/src/FeeModuleMapping.ts | 106 +++++++++++------- fee-tracking-subgraph/subgraph.template.yaml | 9 +- 5 files changed, 209 insertions(+), 112 deletions(-) diff --git a/fee-tracking-subgraph/README.md b/fee-tracking-subgraph/README.md index 272a4f0..b48e22f 100644 --- a/fee-tracking-subgraph/README.md +++ b/fee-tracking-subgraph/README.md @@ -1,55 +1,74 @@ # Fee Tracking Subgraph -This subgraph tracks fee events from the Polymarket FeeModule contract, monitoring the difference between exchange fees and schedule fees, as well as refunds to ensure users only pay the intended fee amount. +This subgraph tracks fee events from the Polymarket FeeModule contract, monitoring fee refunds and withdrawals to ensure transparency in the fee system. ## Purpose The fee tracking subgraph monitors: -- Exchange fees charged by the underlying exchange -- Schedule fees calculated by our fee formula -- Refunds given to users -- Net fees actually paid by users +- Fee refunds given to users when the actual fee charged is higher than the intended fee +- Fee withdrawals by administrators +- Per-user fee statistics and global fee statistics -This helps ensure the fee system works as intended and provides transparency into fee collection. +This helps ensure the fee system works as intended and provides transparency into fee collection and refunds. ## Entities -### FeeEvent -Tracks individual fee events with: -- User who paid the fee -- Exchange fee amount -- Schedule fee amount +### FeeRefund +Tracks individual fee refund events with: +- Order hash that triggered the refund +- Maker address who received the refund +- Token ID (0 for collateral, otherwise CTF token ID) - Refund amount -- Net fee amount +- Original fee amount charged +- Timestamp and transaction details + +### FeeWithdrawal +Tracks fee withdrawal events with: +- Token address +- Recipient address +- Token ID (0 for collateral, otherwise CTF token ID) +- Amount withdrawn - Timestamp and transaction details ### GlobalFeeStats Aggregate statistics across all fee events: -- Total exchange fees charged -- Total schedule fees - Total refunds given -- Total net fees collected -- Total number of fee events +- Total fees withdrawn +- Number of refund events +- Number of withdrawal events ### UserFeeStats Per-user fee statistics: -- User's total exchange fees paid -- User's total schedule fees - User's total refunds received -- User's total net fees paid -- User's fee event count and last event timestamp +- User's total withdrawals +- User's refund event count +- User's withdrawal event count +- Last refund and withdrawal event timestamps ## Usage -Query fee events: +Query fee refunds: ```graphql { - feeEvents(first: 10, orderBy: timestamp, orderDirection: desc) { - user - exchangeFee - scheduleFee + feeRefunds(first: 10, orderBy: timestamp, orderDirection: desc) { + orderHash + maker + tokenId refundAmount - netFee + feeAmount + timestamp + } +} +``` + +Query fee withdrawals: +```graphql +{ + feeWithdrawals(first: 10, orderBy: timestamp, orderDirection: desc) { + token + to + tokenId + amount timestamp } } @@ -59,11 +78,10 @@ Query global statistics: ```graphql { globalFeeStats(id: "") { - totalExchangeFees - totalScheduleFees totalRefunds - totalNetFees - feeEventCount + totalWithdrawals + refundEventCount + withdrawalEventCount } } ``` @@ -72,12 +90,12 @@ Query user statistics: ```graphql { userFeeStats(id: "0xUserAddress") { - totalExchangeFees - totalScheduleFees totalRefunds - totalNetFees - feeEventCount - lastFeeEvent + totalWithdrawals + refundEventCount + withdrawalEventCount + lastRefundEvent + lastWithdrawalEvent } } ``` diff --git a/fee-tracking-subgraph/abis/FeeModule.json b/fee-tracking-subgraph/abis/FeeModule.json index f21e547..0a1b370 100644 --- a/fee-tracking-subgraph/abis/FeeModule.json +++ b/fee-tracking-subgraph/abis/FeeModule.json @@ -2,38 +2,69 @@ { "anonymous": false, "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + }, { "indexed": true, "internalType": "address", - "name": "user", + "name": "maker", "type": "address" }, { "indexed": false, "internalType": "uint256", - "name": "exchangeFee", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refund", "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "scheduleFee", + "name": "feeAmount", "type": "uint256" + } + ], + "name": "FeeRefunded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" }, { "indexed": false, "internalType": "uint256", - "name": "refundAmount", + "name": "id", "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "netFee", + "name": "amount", "type": "uint256" } ], - "name": "FeeCharged", + "name": "FeeWithdrawn", "type": "event" } ] \ No newline at end of file diff --git a/fee-tracking-subgraph/schema.graphql b/fee-tracking-subgraph/schema.graphql index eae9d7f..712267f 100644 --- a/fee-tracking-subgraph/schema.graphql +++ b/fee-tracking-subgraph/schema.graphql @@ -1,17 +1,36 @@ -type FeeEvent @entity { +type FeeRefund @entity { "Transaction hash + log index" id: ID! - "User who paid the fee" - user: String! - "Fee charged by the exchange" - exchangeFee: BigInt! - "Fee according to our fee schedule" - scheduleFee: BigInt! - "Amount refunded to user" + "Order hash" + orderHash: String! + "Maker address who received the refund" + maker: String! + "Token ID (0 for collateral, otherwise CTF token ID)" + tokenId: BigInt! + "Amount refunded" refundAmount: BigInt! - "Net fee actually paid by user" - netFee: BigInt! - "Timestamp of the fee event" + "Original fee amount charged" + feeAmount: BigInt! + "Timestamp of the refund event" + timestamp: BigInt! + "Transaction hash" + txHash: String! + "Block number" + blockNumber: BigInt! +} + +type FeeWithdrawal @entity { + "Transaction hash + log index" + id: ID! + "Token address" + token: String! + "Recipient address" + to: String! + "Token ID (0 for collateral, otherwise CTF token ID)" + tokenId: BigInt! + "Amount withdrawn" + amount: BigInt! + "Timestamp of the withdrawal event" timestamp: BigInt! "Transaction hash" txHash: String! @@ -22,31 +41,29 @@ type FeeEvent @entity { type GlobalFeeStats @entity { "Empty string" id: ID! - "Total exchange fees charged" - totalExchangeFees: BigInt! - "Total schedule fees" - totalScheduleFees: BigInt! "Total refunds given" totalRefunds: BigInt! - "Total net fees collected" - totalNetFees: BigInt! - "Number of fee events" - feeEventCount: BigInt! + "Total fees withdrawn" + totalWithdrawals: BigInt! + "Number of refund events" + refundEventCount: BigInt! + "Number of withdrawal events" + withdrawalEventCount: BigInt! } type UserFeeStats @entity { "User address" id: ID! - "Total exchange fees paid by user" - totalExchangeFees: BigInt! - "Total schedule fees for user" - totalScheduleFees: BigInt! "Total refunds received by user" totalRefunds: BigInt! - "Total net fees paid by user" - totalNetFees: BigInt! - "Number of fee events for user" - feeEventCount: BigInt! - "Last fee event timestamp" - lastFeeEvent: BigInt! + "Total withdrawals by user" + totalWithdrawals: BigInt! + "Number of refund events for user" + refundEventCount: BigInt! + "Number of withdrawal events for user" + withdrawalEventCount: BigInt! + "Last refund event timestamp" + lastRefundEvent: BigInt! + "Last withdrawal event timestamp" + lastWithdrawalEvent: BigInt! } \ No newline at end of file diff --git a/fee-tracking-subgraph/src/FeeModuleMapping.ts b/fee-tracking-subgraph/src/FeeModuleMapping.ts index 113bf09..afe59be 100644 --- a/fee-tracking-subgraph/src/FeeModuleMapping.ts +++ b/fee-tracking-subgraph/src/FeeModuleMapping.ts @@ -1,16 +1,15 @@ import { BigInt } from '@graphprotocol/graph-ts'; -import { FeeCharged } from '../types/FeeModule/FeeModule'; -import { FeeEvent, GlobalFeeStats, UserFeeStats } from '../types/schema'; +import { FeeRefunded, FeeWithdrawn } from '../types/FeeModule/FeeModule'; +import { FeeRefund, FeeWithdrawal, GlobalFeeStats, UserFeeStats } from '../types/schema'; function getGlobalFeeStats(): GlobalFeeStats { let globalStats = GlobalFeeStats.load(''); if (globalStats == null) { globalStats = new GlobalFeeStats(''); - globalStats.totalExchangeFees = BigInt.fromI32(0); - globalStats.totalScheduleFees = BigInt.fromI32(0); globalStats.totalRefunds = BigInt.fromI32(0); - globalStats.totalNetFees = BigInt.fromI32(0); - globalStats.feeEventCount = BigInt.fromI32(0); + globalStats.totalWithdrawals = BigInt.fromI32(0); + globalStats.refundEventCount = BigInt.fromI32(0); + globalStats.withdrawalEventCount = BigInt.fromI32(0); } return globalStats; } @@ -19,55 +18,84 @@ function getUserFeeStats(userAddress: string): UserFeeStats { let userStats = UserFeeStats.load(userAddress); if (userStats == null) { userStats = new UserFeeStats(userAddress); - userStats.totalExchangeFees = BigInt.fromI32(0); - userStats.totalScheduleFees = BigInt.fromI32(0); userStats.totalRefunds = BigInt.fromI32(0); - userStats.totalNetFees = BigInt.fromI32(0); - userStats.feeEventCount = BigInt.fromI32(0); - userStats.lastFeeEvent = BigInt.fromI32(0); + userStats.totalWithdrawals = BigInt.fromI32(0); + userStats.refundEventCount = BigInt.fromI32(0); + userStats.withdrawalEventCount = BigInt.fromI32(0); + userStats.lastRefundEvent = BigInt.fromI32(0); + userStats.lastWithdrawalEvent = BigInt.fromI32(0); } return userStats; } -export function handleFeeCharged(event: FeeCharged): void { +export function handleFeeRefunded(event: FeeRefunded): void { // Extract event parameters - const user = event.params.user.toHexString(); - const exchangeFee = event.params.exchangeFee; - const scheduleFee = event.params.scheduleFee; - const refundAmount = event.params.refundAmount; - const netFee = event.params.netFee; + const orderHash = event.params.orderHash.toHexString(); + const maker = event.params.maker.toHexString(); + const tokenId = event.params.id; + const refundAmount = event.params.refund; + const feeAmount = event.params.feeAmount; - // Create unique ID for the fee event + // Create unique ID for the fee refund event const eventId = event.transaction.hash.toHexString() + '-' + event.logIndex.toString(); - // Create FeeEvent entity - const feeEvent = new FeeEvent(eventId); - feeEvent.user = user; - feeEvent.exchangeFee = exchangeFee; - feeEvent.scheduleFee = scheduleFee; - feeEvent.refundAmount = refundAmount; - feeEvent.netFee = netFee; - feeEvent.timestamp = event.block.timestamp; - feeEvent.txHash = event.transaction.hash.toHexString(); - feeEvent.blockNumber = event.block.number; - feeEvent.save(); + // Create FeeRefund entity + const feeRefund = new FeeRefund(eventId); + feeRefund.orderHash = orderHash; + feeRefund.maker = maker; + feeRefund.tokenId = tokenId; + feeRefund.refundAmount = refundAmount; + feeRefund.feeAmount = feeAmount; + feeRefund.timestamp = event.block.timestamp; + feeRefund.txHash = event.transaction.hash.toHexString(); + feeRefund.blockNumber = event.block.number; + feeRefund.save(); // Update global fee statistics const globalStats = getGlobalFeeStats(); - globalStats.totalExchangeFees = globalStats.totalExchangeFees.plus(exchangeFee); - globalStats.totalScheduleFees = globalStats.totalScheduleFees.plus(scheduleFee); globalStats.totalRefunds = globalStats.totalRefunds.plus(refundAmount); - globalStats.totalNetFees = globalStats.totalNetFees.plus(netFee); - globalStats.feeEventCount = globalStats.feeEventCount.plus(BigInt.fromI32(1)); + globalStats.refundEventCount = globalStats.refundEventCount.plus(BigInt.fromI32(1)); globalStats.save(); // Update user fee statistics - const userStats = getUserFeeStats(user); - userStats.totalExchangeFees = userStats.totalExchangeFees.plus(exchangeFee); - userStats.totalScheduleFees = userStats.totalScheduleFees.plus(scheduleFee); + const userStats = getUserFeeStats(maker); userStats.totalRefunds = userStats.totalRefunds.plus(refundAmount); - userStats.totalNetFees = userStats.totalNetFees.plus(netFee); - userStats.feeEventCount = userStats.feeEventCount.plus(BigInt.fromI32(1)); - userStats.lastFeeEvent = event.block.timestamp; + userStats.refundEventCount = userStats.refundEventCount.plus(BigInt.fromI32(1)); + userStats.lastRefundEvent = event.block.timestamp; + userStats.save(); +} + +export function handleFeeWithdrawn(event: FeeWithdrawn): void { + // Extract event parameters + const token = event.params.token.toHexString(); + const to = event.params.to.toHexString(); + const tokenId = event.params.id; + const amount = event.params.amount; + + // Create unique ID for the fee withdrawal event + const eventId = event.transaction.hash.toHexString() + '-' + event.logIndex.toString(); + + // Create FeeWithdrawal entity + const feeWithdrawal = new FeeWithdrawal(eventId); + feeWithdrawal.token = token; + feeWithdrawal.to = to; + feeWithdrawal.tokenId = tokenId; + feeWithdrawal.amount = amount; + feeWithdrawal.timestamp = event.block.timestamp; + feeWithdrawal.txHash = event.transaction.hash.toHexString(); + feeWithdrawal.blockNumber = event.block.number; + feeWithdrawal.save(); + + // Update global fee statistics + const globalStats = getGlobalFeeStats(); + globalStats.totalWithdrawals = globalStats.totalWithdrawals.plus(amount); + globalStats.withdrawalEventCount = globalStats.withdrawalEventCount.plus(BigInt.fromI32(1)); + globalStats.save(); + + // Update user fee statistics + const userStats = getUserFeeStats(to); + userStats.totalWithdrawals = userStats.totalWithdrawals.plus(amount); + userStats.withdrawalEventCount = userStats.withdrawalEventCount.plus(BigInt.fromI32(1)); + userStats.lastWithdrawalEvent = event.block.timestamp; userStats.save(); } \ No newline at end of file diff --git a/fee-tracking-subgraph/subgraph.template.yaml b/fee-tracking-subgraph/subgraph.template.yaml index 7f04914..fd1e686 100644 --- a/fee-tracking-subgraph/subgraph.template.yaml +++ b/fee-tracking-subgraph/subgraph.template.yaml @@ -14,13 +14,16 @@ dataSources: apiVersion: 0.0.6 language: wasm/assemblyscript entities: - - FeeEvent + - FeeRefund + - FeeWithdrawal - GlobalFeeStats - UserFeeStats abis: - name: FeeModule file: ./abis/FeeModule.json eventHandlers: - - event: FeeCharged(indexed address,uint256,uint256,uint256,uint256) - handler: handleFeeCharged + - event: FeeRefunded(indexed bytes32,indexed address,uint256,uint256,uint256) + handler: handleFeeRefunded + - event: FeeWithdrawn(indexed address,indexed address,uint256,uint256) + handler: handleFeeWithdrawn file: ./src/FeeModuleMapping.ts \ No newline at end of file From 08fbf3cff33e3ff7433a15124a4496fe9a490918 Mon Sep 17 00:00:00 2001 From: Kai Brusch Date: Wed, 30 Jul 2025 13:56:23 -0400 Subject: [PATCH 3/5] Refactor fee tracking: remove block number, split cash/token stats in global and user fee stats --- fee-tracking-subgraph/schema.graphql | 28 ++++++----- fee-tracking-subgraph/src/FeeModuleMapping.ts | 46 +++++++++++++++---- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/fee-tracking-subgraph/schema.graphql b/fee-tracking-subgraph/schema.graphql index 712267f..2974fe7 100644 --- a/fee-tracking-subgraph/schema.graphql +++ b/fee-tracking-subgraph/schema.graphql @@ -15,8 +15,6 @@ type FeeRefund @entity { timestamp: BigInt! "Transaction hash" txHash: String! - "Block number" - blockNumber: BigInt! } type FeeWithdrawal @entity { @@ -34,17 +32,19 @@ type FeeWithdrawal @entity { timestamp: BigInt! "Transaction hash" txHash: String! - "Block number" - blockNumber: BigInt! } type GlobalFeeStats @entity { "Empty string" id: ID! - "Total refunds given" - totalRefunds: BigInt! - "Total fees withdrawn" - totalWithdrawals: BigInt! + "Total cash refunds given" + totalCashRefunds: BigInt! + "Total token refunds given" + totalTokenRefunds: BigInt! + "Total cash fees collected by admins" + totalCashWithdrawals: BigInt! + "Total token fees collected by admins" + totalTokenWithdrawals: BigInt! "Number of refund events" refundEventCount: BigInt! "Number of withdrawal events" @@ -54,10 +54,14 @@ type GlobalFeeStats @entity { type UserFeeStats @entity { "User address" id: ID! - "Total refunds received by user" - totalRefunds: BigInt! - "Total withdrawals by user" - totalWithdrawals: BigInt! + "Total cash refunds received by user" + totalCashRefunds: BigInt! + "Total token refunds received by user" + totalTokenRefunds: BigInt! + "Total cash fees collected by user (if admin)" + totalCashWithdrawals: BigInt! + "Total token fees collected by user (if admin)" + totalTokenWithdrawals: BigInt! "Number of refund events for user" refundEventCount: BigInt! "Number of withdrawal events for user" diff --git a/fee-tracking-subgraph/src/FeeModuleMapping.ts b/fee-tracking-subgraph/src/FeeModuleMapping.ts index afe59be..abb59e3 100644 --- a/fee-tracking-subgraph/src/FeeModuleMapping.ts +++ b/fee-tracking-subgraph/src/FeeModuleMapping.ts @@ -6,8 +6,10 @@ function getGlobalFeeStats(): GlobalFeeStats { let globalStats = GlobalFeeStats.load(''); if (globalStats == null) { globalStats = new GlobalFeeStats(''); - globalStats.totalRefunds = BigInt.fromI32(0); - globalStats.totalWithdrawals = BigInt.fromI32(0); + globalStats.totalCashRefunds = BigInt.fromI32(0); + globalStats.totalTokenRefunds = BigInt.fromI32(0); + globalStats.totalCashWithdrawals = BigInt.fromI32(0); + globalStats.totalTokenWithdrawals = BigInt.fromI32(0); globalStats.refundEventCount = BigInt.fromI32(0); globalStats.withdrawalEventCount = BigInt.fromI32(0); } @@ -18,8 +20,10 @@ function getUserFeeStats(userAddress: string): UserFeeStats { let userStats = UserFeeStats.load(userAddress); if (userStats == null) { userStats = new UserFeeStats(userAddress); - userStats.totalRefunds = BigInt.fromI32(0); - userStats.totalWithdrawals = BigInt.fromI32(0); + userStats.totalCashRefunds = BigInt.fromI32(0); + userStats.totalTokenRefunds = BigInt.fromI32(0); + userStats.totalCashWithdrawals = BigInt.fromI32(0); + userStats.totalTokenWithdrawals = BigInt.fromI32(0); userStats.refundEventCount = BigInt.fromI32(0); userStats.withdrawalEventCount = BigInt.fromI32(0); userStats.lastRefundEvent = BigInt.fromI32(0); @@ -48,18 +52,29 @@ export function handleFeeRefunded(event: FeeRefunded): void { feeRefund.feeAmount = feeAmount; feeRefund.timestamp = event.block.timestamp; feeRefund.txHash = event.transaction.hash.toHexString(); - feeRefund.blockNumber = event.block.number; feeRefund.save(); // Update global fee statistics const globalStats = getGlobalFeeStats(); - globalStats.totalRefunds = globalStats.totalRefunds.plus(refundAmount); + if (tokenId.equals(BigInt.fromI32(0))) { + // Cash refund (tokenId = 0) + globalStats.totalCashRefunds = globalStats.totalCashRefunds.plus(refundAmount); + } else { + // Token refund (tokenId > 0) + globalStats.totalTokenRefunds = globalStats.totalTokenRefunds.plus(refundAmount); + } globalStats.refundEventCount = globalStats.refundEventCount.plus(BigInt.fromI32(1)); globalStats.save(); // Update user fee statistics const userStats = getUserFeeStats(maker); - userStats.totalRefunds = userStats.totalRefunds.plus(refundAmount); + if (tokenId.equals(BigInt.fromI32(0))) { + // Cash refund (tokenId = 0) + userStats.totalCashRefunds = userStats.totalCashRefunds.plus(refundAmount); + } else { + // Token refund (tokenId > 0) + userStats.totalTokenRefunds = userStats.totalTokenRefunds.plus(refundAmount); + } userStats.refundEventCount = userStats.refundEventCount.plus(BigInt.fromI32(1)); userStats.lastRefundEvent = event.block.timestamp; userStats.save(); @@ -83,18 +98,29 @@ export function handleFeeWithdrawn(event: FeeWithdrawn): void { feeWithdrawal.amount = amount; feeWithdrawal.timestamp = event.block.timestamp; feeWithdrawal.txHash = event.transaction.hash.toHexString(); - feeWithdrawal.blockNumber = event.block.number; feeWithdrawal.save(); // Update global fee statistics const globalStats = getGlobalFeeStats(); - globalStats.totalWithdrawals = globalStats.totalWithdrawals.plus(amount); + if (tokenId.equals(BigInt.fromI32(0))) { + // Cash withdrawal (tokenId = 0) + globalStats.totalCashWithdrawals = globalStats.totalCashWithdrawals.plus(amount); + } else { + // Token withdrawal (tokenId > 0) + globalStats.totalTokenWithdrawals = globalStats.totalTokenWithdrawals.plus(amount); + } globalStats.withdrawalEventCount = globalStats.withdrawalEventCount.plus(BigInt.fromI32(1)); globalStats.save(); // Update user fee statistics const userStats = getUserFeeStats(to); - userStats.totalWithdrawals = userStats.totalWithdrawals.plus(amount); + if (tokenId.equals(BigInt.fromI32(0))) { + // Cash withdrawal (tokenId = 0) + userStats.totalCashWithdrawals = userStats.totalCashWithdrawals.plus(amount); + } else { + // Token withdrawal (tokenId > 0) + userStats.totalTokenWithdrawals = userStats.totalTokenWithdrawals.plus(amount); + } userStats.withdrawalEventCount = userStats.withdrawalEventCount.plus(BigInt.fromI32(1)); userStats.lastWithdrawalEvent = event.block.timestamp; userStats.save(); From e58f31ecb942021546ff522c51cacd89c5a5771f Mon Sep 17 00:00:00 2001 From: Kai Brusch Date: Wed, 30 Jul 2025 14:02:23 -0400 Subject: [PATCH 4/5] changed FeeModule --- fee-tracking-subgraph/abis/FeeModule.json | 571 +++++++++++++++++++--- 1 file changed, 504 insertions(+), 67 deletions(-) diff --git a/fee-tracking-subgraph/abis/FeeModule.json b/fee-tracking-subgraph/abis/FeeModule.json index 0a1b370..4ad456f 100644 --- a/fee-tracking-subgraph/abis/FeeModule.json +++ b/fee-tracking-subgraph/abis/FeeModule.json @@ -1,70 +1,507 @@ [ { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "orderHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "maker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "refund", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "feeAmount", - "type": "uint256" - } - ], - "name": "FeeRefunded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "FeeWithdrawn", - "type": "event" + "type": "constructor", + "inputs": [ + { + "name": "_exchange", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addAdmin", + "inputs": [ + { + "name": "admin", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "admins", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "collateral", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ctf", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "exchange", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IExchange" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isAdmin", + "inputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "matchOrders", + "inputs": [ + { + "name": "takerOrder", + "type": "tuple", + "internalType": "struct Order", + "components": [ + { + "name": "salt", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maker", + "type": "address", + "internalType": "address" + }, + { + "name": "signer", + "type": "address", + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "makerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "takerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expiration", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "feeRateBps", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "side", + "type": "uint8", + "internalType": "enum Side" + }, + { + "name": "signatureType", + "type": "uint8", + "internalType": "enum SignatureType" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "makerOrders", + "type": "tuple[]", + "internalType": "struct Order[]", + "components": [ + { + "name": "salt", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maker", + "type": "address", + "internalType": "address" + }, + { + "name": "signer", + "type": "address", + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "makerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "takerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expiration", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "feeRateBps", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "side", + "type": "uint8", + "internalType": "enum Side" + }, + { + "name": "signatureType", + "type": "uint8", + "internalType": "enum SignatureType" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "takerFillAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "makerFillAmounts", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "takerFeeAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "makerFeeAmounts", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "onERC1155BatchReceived", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "onERC1155Received", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeAdmin", + "inputs": [ + { + "name": "admin", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceAdmin", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdrawFees", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "id", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FeeRefunded", + "inputs": [ + { + "name": "orderHash", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "id", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "refund", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "feeCharged", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeeWithdrawn", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "id", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "NewAdmin", + "inputs": [ + { + "name": "admin", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newAdminAddress", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RemovedAdmin", + "inputs": [ + { + "name": "admin", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "removedAdmin", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "NotAdmin", + "inputs": [] } -] \ No newline at end of file +] \ No newline at end of file From cc6fef3386362ac4ebc4d7ef4f48e3f892334c1b Mon Sep 17 00:00:00 2001 From: Kai Brusch Date: Wed, 30 Jul 2025 14:06:25 -0400 Subject: [PATCH 5/5] Update fee tracking: track actual fees charged, fix event parameters to match new ABI --- fee-tracking-subgraph/schema.graphql | 16 ++++++++++++---- fee-tracking-subgraph/src/FeeModuleMapping.ts | 18 +++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/fee-tracking-subgraph/schema.graphql b/fee-tracking-subgraph/schema.graphql index 2974fe7..5aa8559 100644 --- a/fee-tracking-subgraph/schema.graphql +++ b/fee-tracking-subgraph/schema.graphql @@ -3,14 +3,14 @@ type FeeRefund @entity { id: ID! "Order hash" orderHash: String! - "Maker address who received the refund" - maker: String! + "Recipient address who received the refund" + to: String! "Token ID (0 for collateral, otherwise CTF token ID)" tokenId: BigInt! "Amount refunded" refundAmount: BigInt! - "Original fee amount charged" - feeAmount: BigInt! + "Actual fee amount charged by the system" + feeCharged: BigInt! "Timestamp of the refund event" timestamp: BigInt! "Transaction hash" @@ -45,6 +45,10 @@ type GlobalFeeStats @entity { totalCashWithdrawals: BigInt! "Total token fees collected by admins" totalTokenWithdrawals: BigInt! + "Total fees charged by the system (cash)" + totalCashFeesCharged: BigInt! + "Total fees charged by the system (tokens)" + totalTokenFeesCharged: BigInt! "Number of refund events" refundEventCount: BigInt! "Number of withdrawal events" @@ -62,6 +66,10 @@ type UserFeeStats @entity { totalCashWithdrawals: BigInt! "Total token fees collected by user (if admin)" totalTokenWithdrawals: BigInt! + "Total fees charged to user (cash)" + totalCashFeesCharged: BigInt! + "Total fees charged to user (tokens)" + totalTokenFeesCharged: BigInt! "Number of refund events for user" refundEventCount: BigInt! "Number of withdrawal events for user" diff --git a/fee-tracking-subgraph/src/FeeModuleMapping.ts b/fee-tracking-subgraph/src/FeeModuleMapping.ts index abb59e3..e2d3071 100644 --- a/fee-tracking-subgraph/src/FeeModuleMapping.ts +++ b/fee-tracking-subgraph/src/FeeModuleMapping.ts @@ -10,6 +10,8 @@ function getGlobalFeeStats(): GlobalFeeStats { globalStats.totalTokenRefunds = BigInt.fromI32(0); globalStats.totalCashWithdrawals = BigInt.fromI32(0); globalStats.totalTokenWithdrawals = BigInt.fromI32(0); + globalStats.totalCashFeesCharged = BigInt.fromI32(0); + globalStats.totalTokenFeesCharged = BigInt.fromI32(0); globalStats.refundEventCount = BigInt.fromI32(0); globalStats.withdrawalEventCount = BigInt.fromI32(0); } @@ -24,6 +26,8 @@ function getUserFeeStats(userAddress: string): UserFeeStats { userStats.totalTokenRefunds = BigInt.fromI32(0); userStats.totalCashWithdrawals = BigInt.fromI32(0); userStats.totalTokenWithdrawals = BigInt.fromI32(0); + userStats.totalCashFeesCharged = BigInt.fromI32(0); + userStats.totalTokenFeesCharged = BigInt.fromI32(0); userStats.refundEventCount = BigInt.fromI32(0); userStats.withdrawalEventCount = BigInt.fromI32(0); userStats.lastRefundEvent = BigInt.fromI32(0); @@ -35,10 +39,10 @@ function getUserFeeStats(userAddress: string): UserFeeStats { export function handleFeeRefunded(event: FeeRefunded): void { // Extract event parameters const orderHash = event.params.orderHash.toHexString(); - const maker = event.params.maker.toHexString(); + const to = event.params.to.toHexString(); const tokenId = event.params.id; const refundAmount = event.params.refund; - const feeAmount = event.params.feeAmount; + const feeCharged = event.params.feeCharged; // Create unique ID for the fee refund event const eventId = event.transaction.hash.toHexString() + '-' + event.logIndex.toString(); @@ -46,10 +50,10 @@ export function handleFeeRefunded(event: FeeRefunded): void { // Create FeeRefund entity const feeRefund = new FeeRefund(eventId); feeRefund.orderHash = orderHash; - feeRefund.maker = maker; + feeRefund.to = to; feeRefund.tokenId = tokenId; feeRefund.refundAmount = refundAmount; - feeRefund.feeAmount = feeAmount; + feeRefund.feeCharged = feeCharged; feeRefund.timestamp = event.block.timestamp; feeRefund.txHash = event.transaction.hash.toHexString(); feeRefund.save(); @@ -59,21 +63,25 @@ export function handleFeeRefunded(event: FeeRefunded): void { if (tokenId.equals(BigInt.fromI32(0))) { // Cash refund (tokenId = 0) globalStats.totalCashRefunds = globalStats.totalCashRefunds.plus(refundAmount); + globalStats.totalCashFeesCharged = globalStats.totalCashFeesCharged.plus(feeCharged); } else { // Token refund (tokenId > 0) globalStats.totalTokenRefunds = globalStats.totalTokenRefunds.plus(refundAmount); + globalStats.totalTokenFeesCharged = globalStats.totalTokenFeesCharged.plus(feeCharged); } globalStats.refundEventCount = globalStats.refundEventCount.plus(BigInt.fromI32(1)); globalStats.save(); // Update user fee statistics - const userStats = getUserFeeStats(maker); + const userStats = getUserFeeStats(to); if (tokenId.equals(BigInt.fromI32(0))) { // Cash refund (tokenId = 0) userStats.totalCashRefunds = userStats.totalCashRefunds.plus(refundAmount); + userStats.totalCashFeesCharged = userStats.totalCashFeesCharged.plus(feeCharged); } else { // Token refund (tokenId > 0) userStats.totalTokenRefunds = userStats.totalTokenRefunds.plus(refundAmount); + userStats.totalTokenFeesCharged = userStats.totalTokenFeesCharged.plus(feeCharged); } userStats.refundEventCount = userStats.refundEventCount.plus(BigInt.fromI32(1)); userStats.lastRefundEvent = event.block.timestamp;