-
Notifications
You must be signed in to change notification settings - Fork 950
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #573 from voodoo-trade/voodoo-trade
feat: Add fees and DEX dashboard for voodoo trade
- Loading branch information
Showing
2 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/// Project URL: https://voodoo.trade | ||
/// Contact: [email protected] | ||
/// | ||
/// Voodoo Trade is the ultimate FTM-focused perpetual DEX on Fantom Network. | ||
/// Voodoo caters solely to FTM/stable pairs, offering the deepest liquidity and most competitive | ||
/// margin fees available, on par with CEX rates. LPs can earn real yield from both margin trades | ||
/// and swaps on Fantom’s most highly traded pair, with no need to hold any tokens besides FTM | ||
/// and stables. Voodoo is a fair launch platform with support from an array of Fantom Ecosystem | ||
/// stakeholders, and implements a long-term oriented tokenomics system that is the first of its | ||
/// kind for perpetual DEXs. | ||
|
||
import request, { gql } from "graphql-request"; | ||
import { BreakdownAdapter, Fetch } from "../../adapters/types"; | ||
import { CHAIN } from "../../helpers/chains"; | ||
import { getUniqStartOfTodayTimestamp } from "../../helpers/getUniSubgraphVolume"; | ||
|
||
const endpoints: { [key: string]: string } = { | ||
[CHAIN.FANTOM]: "https://api.thegraph.com/subgraphs/name/chicken-juju/voodoo-fantom-stats", | ||
} | ||
|
||
const historicalDataSwap = gql` | ||
query get_volume($period: String!, $id: String!) { | ||
volumeStats(where: {period: $period, id: $id}) { | ||
swap | ||
} | ||
} | ||
`; | ||
|
||
const historicalDataDerivatives = gql` | ||
query get_volume($period: String!, $id: String!) { | ||
volumeStats(where: {period: $period, id: $id}) { | ||
liquidation | ||
margin | ||
} | ||
} | ||
`; | ||
|
||
interface IGraphResponse { | ||
volumeStats: Array<{ | ||
burn: string, | ||
liquidation: string, | ||
margin: string, | ||
mint: string, | ||
swap: string, | ||
}> | ||
} | ||
|
||
const getFetch = (query: string) => (chain: string): Fetch => async (timestamp: number) => { | ||
const dayTimestamp = getUniqStartOfTodayTimestamp(new Date((timestamp * 1000))); | ||
const dailyData: IGraphResponse = await request(endpoints[chain], query, { | ||
id: String(dayTimestamp) + ":daily", | ||
period: "daily", | ||
}); | ||
const totalData: IGraphResponse = await request(endpoints[chain], query, { | ||
id: "total", | ||
period: "total", | ||
}); | ||
|
||
return { | ||
timestamp: dayTimestamp, | ||
dailyVolume: | ||
dailyData.volumeStats.length == 1 | ||
? String(Number(Object.values(dailyData.volumeStats[0]).reduce((sum, element) => String(Number(sum) + Number(element)))) * 10 ** -30) | ||
: undefined, | ||
totalVolume: | ||
totalData.volumeStats.length == 1 | ||
? String(Number(Object.values(totalData.volumeStats[0]).reduce((sum, element) => String(Number(sum) + Number(element)))) * 10 ** -30) | ||
: undefined, | ||
} | ||
} | ||
|
||
const getStartTimestamp = async (chain: string) => { | ||
const startTimestamps: { [chain: string]: number } = { | ||
[CHAIN.FANTOM]: 1686971650, | ||
} | ||
return startTimestamps[chain] | ||
} | ||
|
||
const adapter: BreakdownAdapter = { | ||
breakdown: { | ||
"swap": Object.keys(endpoints).reduce((acc, chain) => { | ||
return { | ||
...acc, | ||
[chain]: { | ||
fetch: getFetch(historicalDataSwap)(chain), | ||
start: async () => getStartTimestamp(chain), | ||
meta: { | ||
methodology: "Volume from FTM <-> stablecoin swaps. Includes both market swaps and limit orders." | ||
} | ||
} | ||
} | ||
}, {}), | ||
"derivatives": Object.keys(endpoints).reduce((acc, chain) => { | ||
return { | ||
...acc, | ||
[chain]: { | ||
fetch: getFetch(historicalDataDerivatives)(chain), | ||
start: async () => getStartTimestamp(chain), | ||
meta: { | ||
methodology: "Volume from leveraged long and short positions on FTM. Includes liquidations and margin fee from market and limit orders." | ||
} | ||
} | ||
} | ||
}, {}) | ||
} | ||
} | ||
|
||
export default adapter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/// Project URL: https://voodoo.trade | ||
/// Contact: [email protected] | ||
/// | ||
/// Voodoo Trade is the ultimate FTM-focused perpetual DEX on Fantom Network. | ||
/// Voodoo caters solely to FTM/stable pairs, offering the deepest liquidity and most competitive | ||
/// margin fees available, on par with CEX rates. LPs can earn real yield from both margin trades | ||
/// and swaps on Fantom’s most highly traded pair, with no need to hold any tokens besides FTM | ||
/// and stables. Voodoo is a fair launch platform with support from an array of Fantom Ecosystem | ||
/// stakeholders, and implements a long-term oriented tokenomics system that is the first of its | ||
/// kind for perpetual DEXs. | ||
|
||
import { GraphQLClient, gql } from "graphql-request"; | ||
import { Adapter } from "../adapters/types"; | ||
import { CHAIN } from "../helpers/chains"; | ||
import { getUniqStartOfTodayTimestamp } from "../helpers/getUniSubgraphVolume"; | ||
|
||
// Smart contract pads values with 10^30. I.e. 10 USD is stored as 10 * 10^30 | ||
const DECIMAL_PLACES = BigInt(10)**BigInt(30); | ||
|
||
const graphQLClient = new GraphQLClient("https://api.thegraph.com/subgraphs/name/chicken-juju/voodoo-fantom-stats"); | ||
|
||
const GET_FEE_BY_ID = gql`query getFeeById($id: ID!) { | ||
feeStat(id: $id) { | ||
id | ||
marginAndLiquidation | ||
swap | ||
mint | ||
burn | ||
} | ||
}`; | ||
|
||
interface FeeStat { | ||
marginAndLiquidation: string; | ||
swap: string; | ||
mint: string; | ||
burn: string; | ||
} | ||
|
||
interface GetFeeByIdResponse { | ||
feeStat: FeeStat | null; | ||
} | ||
|
||
function sumOfFees(feeStat: FeeStat | null): bigint { | ||
if (feeStat == null) { | ||
return BigInt(0); | ||
} | ||
const { marginAndLiquidation, swap, mint, burn } = feeStat; | ||
return BigInt(marginAndLiquidation) + BigInt(swap) + BigInt(mint) + BigInt(burn); | ||
} | ||
|
||
const getFetch = () => async (timestamp: number) => { | ||
const dayTimestamp = getUniqStartOfTodayTimestamp(new Date(timestamp * 1000)); | ||
|
||
const { | ||
feeStat, | ||
} = await graphQLClient.request<GetFeeByIdResponse>(GET_FEE_BY_ID, { | ||
id: `${dayTimestamp}:daily` | ||
}); | ||
|
||
// Hack to retain 2 decimal places. BigInt division doesn't preserve decimal places. | ||
const dailyFees = Number(sumOfFees(feeStat) / (DECIMAL_PLACES / BigInt(100))) / 100; | ||
|
||
const FEE_DISTRIBUTION_PERCENTAGES = { | ||
vmxFtmLp: 30, | ||
vlp: 30, | ||
esVmx: 10, | ||
team: 20, | ||
buyAndBurn: 5, | ||
buyAndAddLiquidity: 5 | ||
} | ||
|
||
const dailySupplySideRevenue = dailyFees * ( | ||
FEE_DISTRIBUTION_PERCENTAGES.vlp | ||
) / (100); | ||
|
||
const dailyHoldersRevenue = dailyFees * ( | ||
FEE_DISTRIBUTION_PERCENTAGES.vmxFtmLp + | ||
FEE_DISTRIBUTION_PERCENTAGES.esVmx + | ||
FEE_DISTRIBUTION_PERCENTAGES.buyAndBurn + | ||
FEE_DISTRIBUTION_PERCENTAGES.buyAndAddLiquidity | ||
) / (100); | ||
|
||
const dailyProtocolRevenue = dailyFees * ( | ||
FEE_DISTRIBUTION_PERCENTAGES.team | ||
) / (100); | ||
|
||
const dailyRevenue = dailyHoldersRevenue + dailyProtocolRevenue; | ||
|
||
return { | ||
timestamp: dayTimestamp, | ||
dailyFees: dailyFees.toFixed(2), | ||
dailyUserFees: dailyFees.toFixed(2), | ||
dailySupplySideRevenue: dailySupplySideRevenue.toFixed(2), | ||
dailyHoldersRevenue: dailyHoldersRevenue.toFixed(2), | ||
dailyProtocolRevenue: dailyProtocolRevenue.toFixed(2), | ||
dailyRevenue: dailyRevenue.toFixed(2) | ||
}; | ||
} | ||
|
||
const methodology = { | ||
Fees: "Fees from open/close position (0.1%), swap (0.18% to 0.8%), mint and burn (based on tokens balance in the pool) and hourly borrow fee ((assets borrowed)/(total assets in pool)*0.0045%)", | ||
UserFees: "Fees from open/close position (0.1%), swap (0.18% to 0.8%) and borrow fee ((assets borrowed)/(total assets in pool)*0.0045%)", | ||
HoldersRevenue: "50% of all collected fees goes to the VMX token- 30% to VMX-FTM LP token stakers, 10% to esVMX stakers, 5% to buy and burns and 5% to buyback and liquidity provisioning", | ||
SupplySideRevenue: "30% of all collected fees goes to VLP holders", | ||
Revenue: "Revenue is 70% of all collected fees, which goes to VMX stakers", | ||
ProtocolRevenue: "20% of all collected fees goes to the treasury" | ||
} | ||
|
||
const adapter: Adapter = { | ||
adapter: { | ||
[CHAIN.FANTOM]: { | ||
fetch: getFetch(), | ||
start: async () => 1686971650, | ||
meta: { | ||
methodology | ||
} | ||
}, | ||
}, | ||
} | ||
|
||
export default adapter; |