From 4087cddc78cbb99f060c1f84fbfbd7d9c4ac0a5a Mon Sep 17 00:00:00 2001 From: samclassix Date: Wed, 30 Jul 2025 10:48:53 +0200 Subject: [PATCH 1/4] feat: Add Frankencoin protocol revenue adapter --- fees/frankencoin/index.ts | 79 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 fees/frankencoin/index.ts diff --git a/fees/frankencoin/index.ts b/fees/frankencoin/index.ts new file mode 100644 index 0000000000..281aa8ae6a --- /dev/null +++ b/fees/frankencoin/index.ts @@ -0,0 +1,79 @@ +import { FetchOptions, SimpleAdapter } from "../../adapters/types"; +import { CHAIN } from "../../helpers/chains"; +import { request, gql } from "graphql-request"; + +// GraphQL endpoint for Frankencoin +const FRANKENCOIN_GRAPH_URL = "https://ponder.test.frankencoin.com"; // TODO: change to production URL + +// ZCHF token address on Ethereum +const ZCHF_ADDRESS = "0xB58E61C3098d85632Df34EecfB899A1Ed80921cB"; + +const PROFIT_LOSS_QUERY = gql` + query GetProfitLoss { + frankencoinProfitLosss( + orderBy: "count", + orderDirection: "desc", + limit: 1, + ) { + items { + profits + losses + } + } + } +`; + +const fetch = async (options: FetchOptions) => { + const dailyFees = options.createBalances(); + const dailyRevenue = options.createBalances(); + + try { + const { frankencoinProfitLosss } = await request(FRANKENCOIN_GRAPH_URL, PROFIT_LOSS_QUERY); + + const profits = frankencoinProfitLosss?.items?.[0]?.profits ?? "0"; + const losses = frankencoinProfitLosss?.items?.[0]?.losses ?? "0"; + + // Convert string values to BigInt for calculations + const profitsBigInt = BigInt(profits); + const lossesBigInt = BigInt(losses); + + // Fees are the total profits in ZCHF + dailyFees.add(ZCHF_ADDRESS, profitsBigInt); + + // Revenue is profits minus losses in ZCHF + const revenue = profitsBigInt - lossesBigInt; + dailyRevenue.add(ZCHF_ADDRESS, revenue); + + return { + dailyFees, + dailyRevenue, + dailyProtocolRevenue: dailyRevenue, + }; + } catch (error) { + console.error("Error fetching Frankencoin data:", error); + return { + dailyFees, + dailyRevenue, + dailyProtocolRevenue: dailyRevenue, + }; + } +}; + +const adapter: SimpleAdapter = { + version: 2, + adapter: { + [CHAIN.ETHEREUM]: { + fetch, + start: '2023-10-28', + meta: { + methodology: { + Fees: "Total profits generated by the Frankencoin protocol in ZCHF tokens", + Revenue: "Net revenue calculated as profits minus losses in ZCHF tokens", + ProtocolRevenue: "All net revenue is retained by the protocol in ZCHF tokens", + } + } + } + } +}; + +export default adapter; \ No newline at end of file From bd5b5958152a184c4933030a53e6a160f1583506 Mon Sep 17 00:00:00 2001 From: samclassix Date: Mon, 15 Sep 2025 14:48:54 +0200 Subject: [PATCH 2/4] feat: refactor fee logic --- fees/frankencoin/index.ts | 160 ++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 65 deletions(-) diff --git a/fees/frankencoin/index.ts b/fees/frankencoin/index.ts index 281aa8ae6a..cf9c5459c6 100644 --- a/fees/frankencoin/index.ts +++ b/fees/frankencoin/index.ts @@ -1,79 +1,109 @@ -import { FetchOptions, SimpleAdapter } from "../../adapters/types"; -import { CHAIN } from "../../helpers/chains"; -import { request, gql } from "graphql-request"; +import { FetchOptions, SimpleAdapter } from '../../adapters/types'; +import { CHAIN } from '../../helpers/chains'; +import { request, gql } from 'graphql-request'; // GraphQL endpoint for Frankencoin -const FRANKENCOIN_GRAPH_URL = "https://ponder.test.frankencoin.com"; // TODO: change to production URL +const FRANKENCOIN_GRAPH_URL = 'https://ponder.frankencoin.com'; // ZCHF token address on Ethereum -const ZCHF_ADDRESS = "0xB58E61C3098d85632Df34EecfB899A1Ed80921cB"; +const ZCHF_ADDRESS = '0xB58E61C3098d85632Df34EecfB899A1Ed80921cB'; -const PROFIT_LOSS_QUERY = gql` - query GetProfitLoss { - frankencoinProfitLosss( - orderBy: "count", - orderDirection: "desc", - limit: 1, - ) { - items { - profits - losses +// query type +type RevenueQuery = { + chainId: number; + count: number; + created: number; + kind: string; + amount: bigint; + profits: bigint; + losses: bigint; +}; + +// fetch function +const fetch = async (options: FetchOptions) => { + const dailyFees = options.createBalances(); + const dailyRevenue = options.createBalances(); + + const PROFIT_LOSS_QUERY = gql` + query { + frankencoinProfitLosss( + where: { + chainId: 1, + created_gt: "${options.startTimestamp}", + created_lte: "${options.endTimestamp}", + } + orderBy: "count", + orderDirection: "desc", + limit: 1000, + ) { + items { + created + kind + amount + } } } - } -`; + `; -const fetch = async (options: FetchOptions) => { - const dailyFees = options.createBalances(); - const dailyRevenue = options.createBalances(); + try { + const { frankencoinProfitLosss } = await request( + FRANKENCOIN_GRAPH_URL, + PROFIT_LOSS_QUERY + ); + + const entries: RevenueQuery[] = frankencoinProfitLosss.items; - try { - const { frankencoinProfitLosss } = await request(FRANKENCOIN_GRAPH_URL, PROFIT_LOSS_QUERY); - - const profits = frankencoinProfitLosss?.items?.[0]?.profits ?? "0"; - const losses = frankencoinProfitLosss?.items?.[0]?.losses ?? "0"; - - // Convert string values to BigInt for calculations - const profitsBigInt = BigInt(profits); - const lossesBigInt = BigInt(losses); - - // Fees are the total profits in ZCHF - dailyFees.add(ZCHF_ADDRESS, profitsBigInt); - - // Revenue is profits minus losses in ZCHF - const revenue = profitsBigInt - lossesBigInt; - dailyRevenue.add(ZCHF_ADDRESS, revenue); - - return { - dailyFees, - dailyRevenue, - dailyProtocolRevenue: dailyRevenue, - }; - } catch (error) { - console.error("Error fetching Frankencoin data:", error); - return { - dailyFees, - dailyRevenue, - dailyProtocolRevenue: dailyRevenue, - }; - } + const profits = entries.filter((e) => e.kind == 'Profit'); + const losses = entries.filter((e) => e.kind == 'Loss'); + + // accumulate + const accumProfits = profits.reduce((a, b) => { + return a + BigInt(b.amount); + }, 0n); + + const accumLosses = losses.reduce((a, b) => { + return a + BigInt(b.amount); + }, 0n); + + // Fees are the total profits in ZCHF + dailyFees.add(ZCHF_ADDRESS, accumLosses); + + // Revenue is profits minus losses in ZCHF + const revenue = accumProfits - accumLosses; + dailyRevenue.add(ZCHF_ADDRESS, revenue > 0n ? revenue : 0n); + + return { + dailyFees, + dailyRevenue, + dailyProtocolRevenue: dailyRevenue, + }; + } catch (error) { + console.error('Error fetching Frankencoin data:', error); + return { + dailyFees, + dailyRevenue, + dailyProtocolRevenue: dailyRevenue, + }; + } }; const adapter: SimpleAdapter = { - version: 2, - adapter: { - [CHAIN.ETHEREUM]: { - fetch, - start: '2023-10-28', - meta: { - methodology: { - Fees: "Total profits generated by the Frankencoin protocol in ZCHF tokens", - Revenue: "Net revenue calculated as profits minus losses in ZCHF tokens", - ProtocolRevenue: "All net revenue is retained by the protocol in ZCHF tokens", - } - } - } - } + version: 2, + adapter: { + [CHAIN.ETHEREUM]: { + fetch, + start: '2023-10-28', + meta: { + methodology: { + Fees: 'Total profits generated by the Frankencoin protocol in ZCHF tokens', + Revenue: + 'Net revenue calculated as profits minus losses in ZCHF tokens', + ProtocolRevenue: + 'All net revenue is retained by the protocol in ZCHF tokens', + }, + }, + }, + }, }; -export default adapter; \ No newline at end of file +export default adapter; From 8cb9d01b2b97a55c5a399253308fe132ac75aa4f Mon Sep 17 00:00:00 2001 From: samclassix Date: Mon, 15 Sep 2025 14:50:09 +0200 Subject: [PATCH 3/4] feat: removing try and catch --- fees/frankencoin/index.ts | 57 +++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/fees/frankencoin/index.ts b/fees/frankencoin/index.ts index cf9c5459c6..5eaeb283e3 100644 --- a/fees/frankencoin/index.ts +++ b/fees/frankencoin/index.ts @@ -45,46 +45,37 @@ const fetch = async (options: FetchOptions) => { } `; - try { - const { frankencoinProfitLosss } = await request( - FRANKENCOIN_GRAPH_URL, - PROFIT_LOSS_QUERY - ); + const { frankencoinProfitLosss } = await request( + FRANKENCOIN_GRAPH_URL, + PROFIT_LOSS_QUERY + ); - const entries: RevenueQuery[] = frankencoinProfitLosss.items; + const entries: RevenueQuery[] = frankencoinProfitLosss.items; - const profits = entries.filter((e) => e.kind == 'Profit'); - const losses = entries.filter((e) => e.kind == 'Loss'); + const profits = entries.filter((e) => e.kind == 'Profit'); + const losses = entries.filter((e) => e.kind == 'Loss'); - // accumulate - const accumProfits = profits.reduce((a, b) => { - return a + BigInt(b.amount); - }, 0n); + // accumulate + const accumProfits = profits.reduce((a, b) => { + return a + BigInt(b.amount); + }, 0n); - const accumLosses = losses.reduce((a, b) => { - return a + BigInt(b.amount); - }, 0n); + const accumLosses = losses.reduce((a, b) => { + return a + BigInt(b.amount); + }, 0n); - // Fees are the total profits in ZCHF - dailyFees.add(ZCHF_ADDRESS, accumLosses); + // Fees are the total profits in ZCHF + dailyFees.add(ZCHF_ADDRESS, accumLosses); - // Revenue is profits minus losses in ZCHF - const revenue = accumProfits - accumLosses; - dailyRevenue.add(ZCHF_ADDRESS, revenue > 0n ? revenue : 0n); + // Revenue is profits minus losses in ZCHF + const revenue = accumProfits - accumLosses; + dailyRevenue.add(ZCHF_ADDRESS, revenue > 0n ? revenue : 0n); - return { - dailyFees, - dailyRevenue, - dailyProtocolRevenue: dailyRevenue, - }; - } catch (error) { - console.error('Error fetching Frankencoin data:', error); - return { - dailyFees, - dailyRevenue, - dailyProtocolRevenue: dailyRevenue, - }; - } + return { + dailyFees, + dailyRevenue, + dailyProtocolRevenue: dailyRevenue, + }; }; const adapter: SimpleAdapter = { From 89adfc9fb93cada1b192d3cdab78d4403d38f36e Mon Sep 17 00:00:00 2001 From: samclassix Date: Mon, 15 Sep 2025 15:24:04 +0200 Subject: [PATCH 4/4] feat: supplySideRevenue for losses and better wording --- fees/frankencoin/index.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/fees/frankencoin/index.ts b/fees/frankencoin/index.ts index 5eaeb283e3..39b145c078 100644 --- a/fees/frankencoin/index.ts +++ b/fees/frankencoin/index.ts @@ -23,12 +23,13 @@ type RevenueQuery = { const fetch = async (options: FetchOptions) => { const dailyFees = options.createBalances(); const dailyRevenue = options.createBalances(); + const dailyProtocolRevenue = options.createBalances(); + const dailySupplySideRevenue = options.createBalances(); const PROFIT_LOSS_QUERY = gql` query { frankencoinProfitLosss( where: { - chainId: 1, created_gt: "${options.startTimestamp}", created_lte: "${options.endTimestamp}", } @@ -65,16 +66,18 @@ const fetch = async (options: FetchOptions) => { }, 0n); // Fees are the total profits in ZCHF - dailyFees.add(ZCHF_ADDRESS, accumLosses); + dailyFees.add(ZCHF_ADDRESS, accumProfits); + dailyRevenue.add(ZCHF_ADDRESS, accumProfits); + dailyProtocolRevenue.add(ZCHF_ADDRESS, accumProfits); - // Revenue is profits minus losses in ZCHF - const revenue = accumProfits - accumLosses; - dailyRevenue.add(ZCHF_ADDRESS, revenue > 0n ? revenue : 0n); + // Interest paid by the protocol + dailySupplySideRevenue.add(ZCHF_ADDRESS, accumLosses); return { dailyFees, dailyRevenue, - dailyProtocolRevenue: dailyRevenue, + dailyProtocolRevenue, + dailySupplySideRevenue, }; }; @@ -86,11 +89,11 @@ const adapter: SimpleAdapter = { start: '2023-10-28', meta: { methodology: { - Fees: 'Total profits generated by the Frankencoin protocol in ZCHF tokens', - Revenue: - 'Net revenue calculated as profits minus losses in ZCHF tokens', - ProtocolRevenue: - 'All net revenue is retained by the protocol in ZCHF tokens', + Fees: 'Profits generated by the Frankencoin protocol (Frankencoin Pool Shares)', + Revenue: 'Net revenue is retained by the protocol', + ProtocolRevenue: 'Net revenue is retained by the protocol', + SupplySideRevenue: + 'Losses absorbed by the protocol, such as interest paid to lenders, auction shortfalls, and similar costs', }, }, },