From 24328a454d6b89684f247d6b8f2c7787a56a4e9e Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Sun, 21 Apr 2024 00:44:24 -0400 Subject: [PATCH 1/8] feat: multichain quest bot --- .env.example | 5 ++- src/config/etherProvider.ts | 8 ++++- src/data/data.json | 16 ++++++--- src/globals/chainIds.ts | 6 ++++ src/listener/ethers/questCreationListener.ts | 11 +++--- src/scripts/createEtherEventListener.ts | 25 +++++++------ src/scripts/getAvailableQuestsForPeriod.ts | 38 ++++++++++++-------- src/scripts/getDecimalsFromToken.ts | 5 +-- src/scripts/getQuestPeriods.ts | 9 +++-- src/scripts/getSymbolFromGauge.ts | 16 ++++++--- src/scripts/getSymbolFromToken.ts | 5 +-- 11 files changed, 96 insertions(+), 48 deletions(-) create mode 100644 src/globals/chainIds.ts diff --git a/.env.example b/.env.example index 8487584..17b5cec 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ DISCORD_TOKEN= -JSON_RPC_URL= +MAINNET_JSON_RPC_URL= +OPTIMISM_JSON_RPC_URL= +POLYGON_JSON_RPC_URL= +ARBITRUM_JSON_RPC_URL= # Twitter TWITTER_API_KEY= diff --git a/src/config/etherProvider.ts b/src/config/etherProvider.ts index e514bfe..5f928d9 100644 --- a/src/config/etherProvider.ts +++ b/src/config/etherProvider.ts @@ -1,6 +1,12 @@ import { ethers } from 'ethers'; import 'dotenv/config'; +import { ChainIds } from '../globals/chainIds'; -const provider = new ethers.JsonRpcProvider(process.env.JSON_RPC_URL); +const provider = { + [ChainIds.MAINNET]: new ethers.JsonRpcProvider(process.env.MAINNET_JSON_RPC_URL), + [ChainIds.ARBITRUM]: new ethers.JsonRpcProvider(process.env.ARBITRUM_JSON_RPC_URL), + [ChainIds.OPTIMISM]: new ethers.JsonRpcProvider(process.env.OPTIMISM_JSON_RPC_URL), + [ChainIds.POLYGON]: new ethers.JsonRpcProvider(process.env.POLYGON_JSON_RPC_URL), +}; export default provider; diff --git a/src/data/data.json b/src/data/data.json index fd5321a..adcb81d 100644 --- a/src/data/data.json +++ b/src/data/data.json @@ -3,8 +3,16 @@ "curveTargetChannelIds": ["1008049103793561670"], "balancerTargetChannelIds": ["1008049911624913018", "999427452789067926"], "fxTargetChannelIds": ["1218099479803199490"], - "veCRVQuestBoardContractAddresses": ["0xF13e938d7a1214ae438761941BC0C651405e68A4"], - "veBALQuestBoardContractAddresses": ["0xf0CeABf99Ddd591BbCC962596B228007eD4624Ae"], - "veLITQuestBoardContractAddresses": ["0x1B921DBD13A280ee14BA6361c1196EB72aaa094e"], - "veFXNQuestBoardContractAddresses": ["0xeB9C1Dbd95dcF5190E6D31Ba94f89593D48990bE"] + "veCRVQuestBoardContractAddresses": { + "1": ["0xF13e938d7a1214ae438761941BC0C651405e68A4"] + }, + "veBALQuestBoardContractAddresses": { + "1": ["0xf0CeABf99Ddd591BbCC962596B228007eD4624Ae"] + }, + "veLITQuestBoardContractAddresses": { + "1": ["0x1B921DBD13A280ee14BA6361c1196EB72aaa094e"] + }, + "veFXNQuestBoardContractAddresses": { + "1": ["0xeB9C1Dbd95dcF5190E6D31Ba94f89593D48990bE"] + } } diff --git a/src/globals/chainIds.ts b/src/globals/chainIds.ts new file mode 100644 index 0000000..8691df3 --- /dev/null +++ b/src/globals/chainIds.ts @@ -0,0 +1,6 @@ +export enum ChainIds { + MAINNET = '1', + OPTIMISM = '10', + ARBITRUM = '42161', + POLYGON = '137', +} diff --git a/src/listener/ethers/questCreationListener.ts b/src/listener/ethers/questCreationListener.ts index c7e6688..56096af 100644 --- a/src/listener/ethers/questCreationListener.ts +++ b/src/listener/ethers/questCreationListener.ts @@ -16,6 +16,7 @@ import formatRewardPerVote from '../../scripts/formatRewardPerVote'; import { ProtocolType } from '../../type/protocolType'; import getQuestPeriod from '../../scripts/getQuestPeriods'; import { QuestType } from '../../type/questType'; +import { ChainIds } from '../../globals/chainIds'; const getChannels = (protocol: ProtocolType): string[] => { switch (protocol) { @@ -174,7 +175,7 @@ const getEmbedColor = (protocol: ProtocolType): number => { }; const questCreationListener = - (protocolType: ProtocolType, questBoardAddress: string): Listener => + (protocolType: ProtocolType, questBoardAddress: string, chainId: ChainIds): Listener => async ( questID: bigint, creator: string, @@ -185,7 +186,7 @@ const questCreationListener = ) => { console.log(`Quest ${questID} created by ${creator} on ${protocolType}`); try { - const periods = await getQuestPeriod(questBoardAddress, questID); + const periods = await getQuestPeriod(questBoardAddress, questID, chainId); const latestPeriod = periods[periods.length - 1]; const maxObjectiveVotes = latestPeriod.maxObjectiveVotes; @@ -196,9 +197,9 @@ const questCreationListener = const questType = minRewardPerVote == maxRewardPerVote ? QuestType.Fixe : QuestType.Range; - const gaugeSymbol = await getSymbolFromGauge(gauge, protocolType); - const rewardTokenSymbol = await getSymbolFromToken(rewardToken); - const rewardTokenDecimals = await getDecimalsFromToken(rewardToken); + const gaugeSymbol = await getSymbolFromGauge(gauge, protocolType, chainId); + const rewardTokenSymbol = await getSymbolFromToken(rewardToken, chainId); + const rewardTokenDecimals = await getDecimalsFromToken(rewardToken, chainId); const totalRewardToken = getTotalRewardToken( rewardAmountPerPeriod, duration, diff --git a/src/scripts/createEtherEventListener.ts b/src/scripts/createEtherEventListener.ts index 27d403c..abfec40 100644 --- a/src/scripts/createEtherEventListener.ts +++ b/src/scripts/createEtherEventListener.ts @@ -1,23 +1,26 @@ import { Contract, Listener } from 'ethers'; import provider from '../config/etherProvider'; import { ProtocolType } from '../type/protocolType'; +import { ChainIds } from '../globals/chainIds'; const createEtherEventListener = ( - addresses: string[], + addresses: { [key: string]: string[] }, abi: any, eventName: string, - listener: (protocolType: ProtocolType, questBoardAddress: string) => Listener, + listener: (protocolType: ProtocolType, questBoardAddress: string, chainId: ChainIds) => Listener, protocolType: ProtocolType, ) => { - addresses.forEach((address) => { - try { - const contract = new Contract(address, abi, provider); - contract.on(eventName, listener(protocolType, address)); - console.log(`Listening to ${address}`); - } catch (err) { - console.error(err); - } - }); + for (const chainId in addresses) { + addresses[chainId].forEach((address) => { + try { + const contract = new Contract(address, abi, provider[chainId as ChainIds]); + contract.on(eventName, listener(protocolType, address, chainId as ChainIds)); + console.log(`Listening to ${address}`); + } catch (err) { + console.error(err); + } + }); + } }; export default createEtherEventListener; diff --git a/src/scripts/getAvailableQuestsForPeriod.ts b/src/scripts/getAvailableQuestsForPeriod.ts index b9a2b1e..8f1e9da 100644 --- a/src/scripts/getAvailableQuestsForPeriod.ts +++ b/src/scripts/getAvailableQuestsForPeriod.ts @@ -2,24 +2,32 @@ import { Contract } from 'ethers'; import { WEEK } from '../globals/time'; import provider from '../config/etherProvider'; import QuestBoardAbi from '../data/abi/QuestBoardAbi.json'; +import { ChainIds } from '../globals/chainIds'; -const getAvailableQuestsForPeriod = async (addresses: string[]): Promise => { - let amount = 0n; - - await Promise.all( - addresses.map(async (address) => { - try { - const contract = new Contract(address, QuestBoardAbi, provider); - const availableQuestsNb = await contract.getQuestIdsForPeriod( - (BigInt(Date.now()) / 1000n / WEEK) * WEEK, - ); - amount = amount + BigInt(availableQuestsNb.length); - } catch (err) { - console.error(err); - } +const getAvailableQuestsForPeriod = async (addresses: { + [key: string]: string[]; +}): Promise => { + const amounts = await Promise.all( + Object.keys(addresses).map(async (chainId) => { + let amount = 0n; + await Promise.all( + addresses[chainId].map(async (address) => { + try { + const contract = new Contract(address, QuestBoardAbi, provider[chainId as ChainIds]); + const availableQuestsNb = await contract.getQuestIdsForPeriod( + (BigInt(Date.now()) / 1000n / WEEK) * WEEK, + ); + amount = amount + BigInt(availableQuestsNb.length); + } catch (err) { + console.error(err); + } + }), + ); + return amount; }), ); - return amount; + + return amounts.reduce((a, b) => a + b, 0n); }; export default getAvailableQuestsForPeriod; diff --git a/src/scripts/getDecimalsFromToken.ts b/src/scripts/getDecimalsFromToken.ts index b669aa1..cac323e 100644 --- a/src/scripts/getDecimalsFromToken.ts +++ b/src/scripts/getDecimalsFromToken.ts @@ -1,10 +1,11 @@ import { Contract } from 'ethers'; import provider from '../config/etherProvider'; import ERC20 from '../data/abi/ERC20.json'; +import { ChainIds } from '../globals/chainIds'; -const getDecimalsFromToken = async (tokenAddress: string): Promise => { +const getDecimalsFromToken = async (tokenAddress: string, chainId: ChainIds): Promise => { try { - const tokenContract = new Contract(tokenAddress, ERC20, provider); + const tokenContract = new Contract(tokenAddress, ERC20, provider[chainId]); const decimals = await tokenContract.decimals(); return decimals; } catch (err) { diff --git a/src/scripts/getQuestPeriods.ts b/src/scripts/getQuestPeriods.ts index 3206bc5..742a6b2 100644 --- a/src/scripts/getQuestPeriods.ts +++ b/src/scripts/getQuestPeriods.ts @@ -1,6 +1,7 @@ import { Contract } from 'ethers'; import provider from '../config/etherProvider'; import QuestBoardAbi from '../data/abi/QuestBoardAbi.json'; +import { ChainIds } from '../globals/chainIds'; enum PeriodState { ZERO, @@ -28,8 +29,12 @@ type QuestPeriod = { currentState: PeriodState; }; -const getQuestPeriod = async (questBoard: string, questId: bigint): Promise => { - const contract = new Contract(questBoard, QuestBoardAbi, provider); +const getQuestPeriod = async ( + questBoard: string, + questId: bigint, + chainId: ChainIds, +): Promise => { + const contract = new Contract(questBoard, QuestBoardAbi, provider[chainId]); const periods = await contract.getAllQuestPeriodsForQuestId(questId); return periods; }; diff --git a/src/scripts/getSymbolFromGauge.ts b/src/scripts/getSymbolFromGauge.ts index 5d5e97e..d1da60b 100644 --- a/src/scripts/getSymbolFromGauge.ts +++ b/src/scripts/getSymbolFromGauge.ts @@ -1,8 +1,9 @@ import axios from 'axios'; import { ProtocolType } from '../type/protocolType'; -import { Contract, getAddress } from 'ethers'; -import provider from '../config/etherProvider'; +import { Contract, Provider, getAddress } from 'ethers'; +import etherProvider from '../config/etherProvider'; import FxGauge from '../data/abi/FxGauge.json'; +import { ChainIds } from '../globals/chainIds'; const getSymbolFromBalancerGauge = async (gauge: string): Promise => { try { @@ -92,7 +93,7 @@ const getSymbolFromCurveLp = async (expectedLp: string): Promise => { return ''; }; -const getSymbolFromFxGauge = async (gauge: string): Promise => { +const getSymbolFromFxGauge = async (gauge: string, provider: Provider): Promise => { try { const gaugeContract = new Contract(gauge, FxGauge, provider); const stakingToken = await gaugeContract.stakingToken(); @@ -110,7 +111,12 @@ const getSymbolFromFxGauge = async (gauge: string): Promise => { } }; -const getSymbolFromGauge = async (gauge: string, protocol: ProtocolType): Promise => { +const getSymbolFromGauge = async ( + gauge: string, + protocol: ProtocolType, + chainId: ChainIds, +): Promise => { + const provider = etherProvider[chainId]; switch (protocol) { case ProtocolType.Balancer: return getSymbolFromBalancerGauge(gauge); @@ -119,7 +125,7 @@ const getSymbolFromGauge = async (gauge: string, protocol: ProtocolType): Promis case ProtocolType.Bunni: return getSymbolFromBunniGauge(gauge); case ProtocolType.Fx: - return getSymbolFromFxGauge(gauge); + return getSymbolFromFxGauge(gauge, provider); default: return ''; } diff --git a/src/scripts/getSymbolFromToken.ts b/src/scripts/getSymbolFromToken.ts index f05a1f4..d5fe612 100644 --- a/src/scripts/getSymbolFromToken.ts +++ b/src/scripts/getSymbolFromToken.ts @@ -1,10 +1,11 @@ import { Contract } from 'ethers'; import provider from '../config/etherProvider'; import ERC20 from '../data/abi/ERC20.json'; +import { ChainIds } from '../globals/chainIds'; -const getSymbolFromToken = async (tokenAddress: string): Promise => { +const getSymbolFromToken = async (tokenAddress: string, chainId: ChainIds): Promise => { try { - const tokenContract = new Contract(tokenAddress, ERC20, provider); + const tokenContract = new Contract(tokenAddress, ERC20, provider[chainId]); const symbol = await tokenContract.symbol(); return symbol; } catch (err) { From 5273f0bddf3dfe74a32b82025a6836b3d06a07e6 Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Mon, 22 Apr 2024 22:32:54 -0400 Subject: [PATCH 2/8] feat: add questboards on all chains --- src/data/data.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/data/data.json b/src/data/data.json index adcb81d..1b78d16 100644 --- a/src/data/data.json +++ b/src/data/data.json @@ -4,13 +4,19 @@ "balancerTargetChannelIds": ["1008049911624913018", "999427452789067926"], "fxTargetChannelIds": ["1218099479803199490"], "veCRVQuestBoardContractAddresses": { - "1": ["0xF13e938d7a1214ae438761941BC0C651405e68A4"] + "1": ["0xF13e938d7a1214ae438761941BC0C651405e68A4"], + "42161": ["0x2Aa638596429e4734D872feC6e7a42e3f3D9fBf8"], + "137": ["0x089154A7E4C562d5998AB3D7Ca57B504A8912482"] }, "veBALQuestBoardContractAddresses": { - "1": ["0xf0CeABf99Ddd591BbCC962596B228007eD4624Ae"] + "1": ["0xf0CeABf99Ddd591BbCC962596B228007eD4624Ae"], + "42161": ["0x8EdcFE9Bc7d2a735117B94C16456D8303777abbb"], + "10": ["0x12Da7E0c469CEeC4EFADa2F5E8CAedCD3F3E6748"], + "137": ["0x0482A2d6e2F895125b7237de70c675cd55FE17Ca"] }, "veLITQuestBoardContractAddresses": { - "1": ["0x1B921DBD13A280ee14BA6361c1196EB72aaa094e"] + "1": ["0x1B921DBD13A280ee14BA6361c1196EB72aaa094e"], + "42161": ["0x04C70Abaa9D3eB14F090094eDc72D5581Dc65A22"] }, "veFXNQuestBoardContractAddresses": { "1": ["0xeB9C1Dbd95dcF5190E6D31Ba94f89593D48990bE"] From e008b84d184d87e3ef8942c0efdb779145df589f Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Sat, 27 Apr 2024 01:16:16 -0400 Subject: [PATCH 3/8] feat: polygon zkevm board --- .env.example | 1 + src/config/etherProvider.ts | 1 + src/data/data.json | 3 ++- src/globals/chainIds.ts | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 17b5cec..9edff5c 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,7 @@ MAINNET_JSON_RPC_URL= OPTIMISM_JSON_RPC_URL= POLYGON_JSON_RPC_URL= ARBITRUM_JSON_RPC_URL= +POLYGON_ZKEVM_JSON_RPC_URL= # Twitter TWITTER_API_KEY= diff --git a/src/config/etherProvider.ts b/src/config/etherProvider.ts index 5f928d9..a8fda96 100644 --- a/src/config/etherProvider.ts +++ b/src/config/etherProvider.ts @@ -3,6 +3,7 @@ import 'dotenv/config'; import { ChainIds } from '../globals/chainIds'; const provider = { + [ChainIds.POLYGON_ZKEVM]: new ethers.JsonRpcProvider(process.env.POLYGON_ZKEVM_JSON_RPC_URL), [ChainIds.MAINNET]: new ethers.JsonRpcProvider(process.env.MAINNET_JSON_RPC_URL), [ChainIds.ARBITRUM]: new ethers.JsonRpcProvider(process.env.ARBITRUM_JSON_RPC_URL), [ChainIds.OPTIMISM]: new ethers.JsonRpcProvider(process.env.OPTIMISM_JSON_RPC_URL), diff --git a/src/data/data.json b/src/data/data.json index 1b78d16..fe3e43f 100644 --- a/src/data/data.json +++ b/src/data/data.json @@ -12,7 +12,8 @@ "1": ["0xf0CeABf99Ddd591BbCC962596B228007eD4624Ae"], "42161": ["0x8EdcFE9Bc7d2a735117B94C16456D8303777abbb"], "10": ["0x12Da7E0c469CEeC4EFADa2F5E8CAedCD3F3E6748"], - "137": ["0x0482A2d6e2F895125b7237de70c675cd55FE17Ca"] + "137": ["0x0482A2d6e2F895125b7237de70c675cd55FE17Ca"], + "1101": ["0x2Aa638596429e4734D872feC6e7a42e3f3D9fBf8"] }, "veLITQuestBoardContractAddresses": { "1": ["0x1B921DBD13A280ee14BA6361c1196EB72aaa094e"], diff --git a/src/globals/chainIds.ts b/src/globals/chainIds.ts index 8691df3..ad799c9 100644 --- a/src/globals/chainIds.ts +++ b/src/globals/chainIds.ts @@ -3,4 +3,5 @@ export enum ChainIds { OPTIMISM = '10', ARBITRUM = '42161', POLYGON = '137', + POLYGON_ZKEVM = '1101', } From 2d3d7f49729ad36b6d4faedea903ac0a30bc98d2 Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Wed, 7 Aug 2024 14:56:15 +0200 Subject: [PATCH 4/8] feat: quest board v2.1 --- src/data/data.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data/data.json b/src/data/data.json index fe3e43f..1adcfca 100644 --- a/src/data/data.json +++ b/src/data/data.json @@ -4,22 +4,22 @@ "balancerTargetChannelIds": ["1008049911624913018", "999427452789067926"], "fxTargetChannelIds": ["1218099479803199490"], "veCRVQuestBoardContractAddresses": { - "1": ["0xF13e938d7a1214ae438761941BC0C651405e68A4"], + "1": ["0xAa1698f0A51e6d00F5533cc3E5D36010ee4558C6"], "42161": ["0x2Aa638596429e4734D872feC6e7a42e3f3D9fBf8"], "137": ["0x089154A7E4C562d5998AB3D7Ca57B504A8912482"] }, "veBALQuestBoardContractAddresses": { - "1": ["0xf0CeABf99Ddd591BbCC962596B228007eD4624Ae"], + "1": ["0xfEb352930cA196a80B708CDD5dcb4eCA94805daB", "0xfd9F19A9B91BecAE3c8dABC36CDd1eA86Fc1A222"], "42161": ["0x8EdcFE9Bc7d2a735117B94C16456D8303777abbb"], "10": ["0x12Da7E0c469CEeC4EFADa2F5E8CAedCD3F3E6748"], "137": ["0x0482A2d6e2F895125b7237de70c675cd55FE17Ca"], "1101": ["0x2Aa638596429e4734D872feC6e7a42e3f3D9fBf8"] }, "veLITQuestBoardContractAddresses": { - "1": ["0x1B921DBD13A280ee14BA6361c1196EB72aaa094e"], + "1": ["0xDD3cbe4E0f10910eb435EA6DBe97469ABc3d7e9c"], "42161": ["0x04C70Abaa9D3eB14F090094eDc72D5581Dc65A22"] }, "veFXNQuestBoardContractAddresses": { - "1": ["0xeB9C1Dbd95dcF5190E6D31Ba94f89593D48990bE"] + "1": ["0x59AbF8642f4c7D8d3C5633eDCBBf6B12234FF02D"] } } From d1af5308abd8f4db947483db3ce1ca8db3fe2e08 Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Wed, 14 Aug 2024 15:20:54 +0200 Subject: [PATCH 5/8] style: prettier json --- package.json | 4 +- src/data/abi/ERC20.json | 84 +++++------ src/data/abi/FxGauge.json | 42 +++--- src/data/abi/QuestBoardAbi.json | 255 ++++++++++++++++++-------------- src/data/data.json | 5 +- 5 files changed, 217 insertions(+), 173 deletions(-) diff --git a/package.json b/package.json index 70161c8..467813f 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "build": "tsc", "dev": "ts-node src/index.ts", - "lint": "eslint --ext .ts,.js src", - "lint:fix": "eslint --ext .ts,.js src --fix", + "lint": "eslint --ext .ts,.js,.json src", + "lint:fix": "eslint --ext .ts,.js,.json src --fix", "start": "node dist/" }, "keywords": [], diff --git a/src/data/abi/ERC20.json b/src/data/abi/ERC20.json index 7f28a6f..86cfcbd 100644 --- a/src/data/abi/ERC20.json +++ b/src/data/abi/ERC20.json @@ -1,43 +1,43 @@ [ - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -] \ No newline at end of file + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/data/abi/FxGauge.json b/src/data/abi/FxGauge.json index 4ede8c1..433ef5b 100644 --- a/src/data/abi/FxGauge.json +++ b/src/data/abi/FxGauge.json @@ -1,22 +1,22 @@ [ - { - "inputs": [], - "name": "stakingToken", - "outputs":[{"internalType":"address","name":"","type":"address"}], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -] \ No newline at end of file + { + "inputs": [], + "name": "stakingToken", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/data/abi/QuestBoardAbi.json b/src/data/abi/QuestBoardAbi.json index d86200d..2028795 100644 --- a/src/data/abi/QuestBoardAbi.json +++ b/src/data/abi/QuestBoardAbi.json @@ -1,125 +1,166 @@ [ - { - "anonymous": false, - "inputs": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "questID", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "gauge", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "rewardToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "duration", + "type": "uint48" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "startPeriod", + "type": "uint256" + } + ], + "name": "NewQuest", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "questId", + "type": "uint256" + } + ], + "name": "getAllQuestPeriodsForQuestId", + "outputs": [ + { + "components": [ { - "indexed": true, "internalType": "uint256", - "name": "questID", + "name": "rewardAmountPerPeriod", "type": "uint256" }, { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" + "internalType": "uint256", + "name": "minRewardPerVote", + "type": "uint256" }, { - "indexed": true, - "internalType": "address", - "name": "gauge", - "type": "address" + "internalType": "uint256", + "name": "maxRewardPerVote", + "type": "uint256" }, { - "indexed": false, - "internalType": "address", - "name": "rewardToken", - "type": "address" + "internalType": "uint256", + "name": "minObjectiveVotes", + "type": "uint256" }, { - "indexed": false, - "internalType": "uint48", - "name": "duration", - "type": "uint48" + "internalType": "uint256", + "name": "maxObjectiveVotes", + "type": "uint256" }, { - "indexed": false, "internalType": "uint256", - "name": "startPeriod", + "name": "rewardAmountDistributed", "type": "uint256" + }, + { + "internalType": "uint48", + "name": "periodStart", + "type": "uint48" + }, + { + "internalType": "enum QuestDataTypes.PeriodState", + "name": "currentState", + "type": "uint8" + } + ], + "internalType": "struct IQuestBoard.QuestPeriod[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "period", + "type": "uint256" + } + ], + "name": "getQuestIdsForPeriod", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "quests", + "outputs": [ + { "internalType": "address", "name": "creator", "type": "address" }, + { "internalType": "address", "name": "rewardToken", "type": "address" }, + { "internalType": "address", "name": "gauge", "type": "address" }, + { "internalType": "uint48", "name": "duration", "type": "uint48" }, + { "internalType": "uint48", "name": "periodStart", "type": "uint48" }, + { "internalType": "uint256", "name": "totalRewardAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "rewardAmountPerPeriod", "type": "uint256" }, + { "internalType": "uint256", "name": "minRewardPerVote", "type": "uint256" }, + { "internalType": "uint256", "name": "maxRewardPerVote", "type": "uint256" }, + { "internalType": "uint256", "name": "minObjectiveVotes", "type": "uint256" }, + { "internalType": "uint256", "name": "maxObjectiveVotes", "type": "uint256" }, + { + "components": [ + { + "internalType": "enum QuestDataTypes.QuestVoteType", + "name": "voteType", + "type": "uint8" + }, + { + "internalType": "enum QuestDataTypes.QuestRewardsType", + "name": "rewardsType", + "type": "uint8" + }, + { + "internalType": "enum QuestDataTypes.QuestCloseType", + "name": "closeType", + "type": "uint8" } ], - "name": "NewQuest", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "questId", - "type": "uint256" - } - ], - "name": "getAllQuestPeriodsForQuestId", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "rewardAmountPerPeriod", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minRewardPerVote", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxRewardPerVote", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minObjectiveVotes", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxObjectiveVotes", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "rewardAmountDistributed", - "type": "uint256" - }, - { - "internalType": "uint48", - "name": "periodStart", - "type": "uint48" - }, - { - "internalType": "enum QuestDataTypes.PeriodState", - "name": "currentState", - "type": "uint8" - } - ], - "internalType": "struct IQuestBoard.QuestPeriod[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "period", - "type": "uint256" - } - ], - "name": "getQuestIdsForPeriod", - "outputs": [ - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - } -] \ No newline at end of file + "internalType": "struct IQuestBoard.QuestTypes", + "name": "types", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/data/data.json b/src/data/data.json index 1adcfca..586b36f 100644 --- a/src/data/data.json +++ b/src/data/data.json @@ -9,7 +9,10 @@ "137": ["0x089154A7E4C562d5998AB3D7Ca57B504A8912482"] }, "veBALQuestBoardContractAddresses": { - "1": ["0xfEb352930cA196a80B708CDD5dcb4eCA94805daB", "0xfd9F19A9B91BecAE3c8dABC36CDd1eA86Fc1A222"], + "1": [ + "0xfEb352930cA196a80B708CDD5dcb4eCA94805daB", + "0xfd9F19A9B91BecAE3c8dABC36CDd1eA86Fc1A222" + ], "42161": ["0x8EdcFE9Bc7d2a735117B94C16456D8303777abbb"], "10": ["0x12Da7E0c469CEeC4EFADa2F5E8CAedCD3F3E6748"], "137": ["0x0482A2d6e2F895125b7237de70c675cd55FE17Ca"], From 71d030f005235dc3b61f8e553877e63fa449814b Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Wed, 14 Aug 2024 15:21:28 +0200 Subject: [PATCH 6/8] fix: get name of bunni gauges without hosted subgraph --- .env.example | 1 + src/scripts/getSymbolFromGauge.ts | 32 +++++++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 9edff5c..6e60c0a 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ OPTIMISM_JSON_RPC_URL= POLYGON_JSON_RPC_URL= ARBITRUM_JSON_RPC_URL= POLYGON_ZKEVM_JSON_RPC_URL= +SUBGRAPH_APIKEY= # Twitter TWITTER_API_KEY= diff --git a/src/scripts/getSymbolFromGauge.ts b/src/scripts/getSymbolFromGauge.ts index d1da60b..6212bf3 100644 --- a/src/scripts/getSymbolFromGauge.ts +++ b/src/scripts/getSymbolFromGauge.ts @@ -40,11 +40,23 @@ const getSymbolFromCurveGauge = async (expectedGauge: string): Promise = }; const getBunniChainGauges = async ( - chain: string, + chain: ChainIds, ): Promise<{ address: string; symbol: string }[]> => { + let subgrapghId: string; + switch (chain) { + case ChainIds.MAINNET: + subgrapghId = 'HH4HFj4rFnm5qnkb8MbEdP2V5eD9rZnLJE921YQAs7AV'; + break; + case ChainIds.ARBITRUM: + subgrapghId = 'QmQNz7JfUh4ztY5S8w5dJggyCsGmxYZSgJRd8NRHsJ3KUN'; + break; + default: + return []; + } + try { const res = await axios.post( - `https://api.thegraph.com/subgraphs/name/bunniapp/bunni-${chain}`, + `https://gateway.thegraph.com/api/${process.env.SUBGRAPH_APIKEY}/subgraphs/id/${subgrapghId}`, { query: '{\n bunniTokens(\n where: {gauge_: {address_not: "0x0000000000000000000000000000000000000000"}}\n ) {\n gauge {\n address\n }\n name\n }\n}', @@ -64,15 +76,11 @@ const getBunniChainGauges = async ( } }; -const getSymbolFromBunniGauge = async (expectedGauge: string): Promise => { - const chains = ['mainnet', 'arbitrum']; - - for (const chain of chains) { - const gauges = await getBunniChainGauges(chain); - for (const gauge of gauges) { - if (getAddress(gauge.address) === getAddress(expectedGauge)) { - return gauge.symbol.replace('Bunni ', '').replace(' LP', ''); - } +const getSymbolFromBunniGauge = async (expectedGauge: string, chain: ChainIds): Promise => { + const gauges = await getBunniChainGauges(chain); + for (const gauge of gauges) { + if (getAddress(gauge.address) === getAddress(expectedGauge)) { + return gauge.symbol.replace('Bunni ', '').replace(' LP', ''); } } return ''; @@ -123,7 +131,7 @@ const getSymbolFromGauge = async ( case ProtocolType.Curve: return getSymbolFromCurveGauge(gauge); case ProtocolType.Bunni: - return getSymbolFromBunniGauge(gauge); + return getSymbolFromBunniGauge(gauge, chainId); case ProtocolType.Fx: return getSymbolFromFxGauge(gauge, provider); default: From a1624561594eec7eb11ff3b7ae379da4d44453c0 Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Wed, 14 Aug 2024 15:21:41 +0200 Subject: [PATCH 7/8] fix: correct way to get new quests --- src/listener/ethers/questCreationListener.ts | 35 +++++++--- src/scripts/getAvailableQuestsForPeriod.ts | 2 +- src/scripts/getQuest.ts | 68 ++++++++++++++++++++ 3 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 src/scripts/getQuest.ts diff --git a/src/listener/ethers/questCreationListener.ts b/src/listener/ethers/questCreationListener.ts index 56096af..80cec0d 100644 --- a/src/listener/ethers/questCreationListener.ts +++ b/src/listener/ethers/questCreationListener.ts @@ -17,6 +17,7 @@ import { ProtocolType } from '../../type/protocolType'; import getQuestPeriod from '../../scripts/getQuestPeriods'; import { QuestType } from '../../type/questType'; import { ChainIds } from '../../globals/chainIds'; +import { getQuest, RewardsType } from '../../scripts/getQuest'; const getChannels = (protocol: ProtocolType): string[] => { switch (protocol) { @@ -186,16 +187,34 @@ const questCreationListener = ) => { console.log(`Quest ${questID} created by ${creator} on ${protocolType}`); try { - const periods = await getQuestPeriod(questBoardAddress, questID, chainId); - const latestPeriod = periods[periods.length - 1]; + let maxObjectiveVotes: bigint; + let maxRewardPerVote: bigint; + let minObjectiveVotes: bigint; + let minRewardPerVote: bigint; + let rewardAmountPerPeriod: bigint; + let questType: QuestType; + if (chainId === ChainIds.MAINNET) { + const quest = await getQuest(questBoardAddress, questID, chainId); - const maxObjectiveVotes = latestPeriod.maxObjectiveVotes; - const maxRewardPerVote = latestPeriod.maxRewardPerVote; - const minObjectiveVotes = latestPeriod.minObjectiveVotes; - const minRewardPerVote = latestPeriod.minRewardPerVote; - const rewardAmountPerPeriod = latestPeriod.rewardAmountPerPeriod; + maxObjectiveVotes = quest.maxObjectiveVotes; + maxRewardPerVote = quest.maxRewardPerVote; + minObjectiveVotes = quest.minObjectiveVotes; + minRewardPerVote = quest.minRewardPerVote; + rewardAmountPerPeriod = quest.rewardAmountPerPeriod; - const questType = minRewardPerVote == maxRewardPerVote ? QuestType.Fixe : QuestType.Range; + questType = quest.types.rewardsType == RewardsType.FIXED ? QuestType.Fixe : QuestType.Range; + } else { + const periods = await getQuestPeriod(questBoardAddress, questID, chainId); + const latestPeriod = periods[periods.length - 1]; + + maxObjectiveVotes = latestPeriod.maxObjectiveVotes; + maxRewardPerVote = latestPeriod.maxRewardPerVote; + minObjectiveVotes = latestPeriod.minObjectiveVotes; + minRewardPerVote = latestPeriod.minRewardPerVote; + rewardAmountPerPeriod = latestPeriod.rewardAmountPerPeriod; + + questType = minRewardPerVote == maxRewardPerVote ? QuestType.Fixe : QuestType.Range; + } const gaugeSymbol = await getSymbolFromGauge(gauge, protocolType, chainId); const rewardTokenSymbol = await getSymbolFromToken(rewardToken, chainId); diff --git a/src/scripts/getAvailableQuestsForPeriod.ts b/src/scripts/getAvailableQuestsForPeriod.ts index 8f1e9da..4f35895 100644 --- a/src/scripts/getAvailableQuestsForPeriod.ts +++ b/src/scripts/getAvailableQuestsForPeriod.ts @@ -17,7 +17,7 @@ const getAvailableQuestsForPeriod = async (addresses: { const availableQuestsNb = await contract.getQuestIdsForPeriod( (BigInt(Date.now()) / 1000n / WEEK) * WEEK, ); - amount = amount + BigInt(availableQuestsNb.length); + amount += BigInt(availableQuestsNb.length); } catch (err) { console.error(err); } diff --git a/src/scripts/getQuest.ts b/src/scripts/getQuest.ts new file mode 100644 index 0000000..b2020ba --- /dev/null +++ b/src/scripts/getQuest.ts @@ -0,0 +1,68 @@ +import { Contract } from 'ethers'; +import provider from '../config/etherProvider'; +import QuestBoardAbi from '../data/abi/QuestBoardAbi.json'; +import { ChainIds } from '../globals/chainIds'; + +enum VoteType { + NORMAL, + BLACKLIST, + WHITELIST, +} + +enum RewardsType { + FIXED, + RANGE, +} + +enum CloseType { + NORMAL, + ROLLOVER, + DISTRIBUTE, +} + +type QuestTypes = { + voteType: VoteType; + rewardsType: RewardsType; + closeType: CloseType; +}; + +type QuestPeriod = { + // Address of the Quest creator (caller of createQuest() method) + creator: string; + // Address of the ERC20 used for rewards + rewardToken: string; + // Address of the target Gauge + gauge: string; + // Total number of periods for the Quest + duration: bigint; + // Timestamp where the 1st QuestPeriod starts + periodStart: bigint; + // Total amount of rewards paid for this Quest + // If changes were made to the parameters of this Quest, this will account + // any added reward amounts + totalRewardAmount: bigint; + // Total reward amount that can be distributed for each period + rewardAmountPerPeriod: bigint; + // Min Amount of reward for each vote (for 1 veToken) + minRewardPerVote: bigint; + // Max Amount of reward for each vote (for 1 veToken) + maxRewardPerVote: bigint; + // Min Target Bias for the Gauge + minObjectiveVotes: bigint; + // Max Target Bias for the Gauge + maxObjectiveVotes: bigint; + // Quest Types + types: QuestTypes; +}; + +const getQuest = async ( + questBoard: string, + questId: bigint, + chainId: ChainIds, +): Promise => { + const contract = new Contract(questBoard, QuestBoardAbi, provider[chainId]); + const quest = await contract.quests(questId); + return quest; +}; + +export { getQuest, QuestPeriod, QuestTypes, VoteType, RewardsType, CloseType }; From e04342b1359369f36585de134add39586b840975 Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Wed, 14 Aug 2024 15:25:25 +0200 Subject: [PATCH 8/8] chore: remove json to lint --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 467813f..70161c8 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "build": "tsc", "dev": "ts-node src/index.ts", - "lint": "eslint --ext .ts,.js,.json src", - "lint:fix": "eslint --ext .ts,.js,.json src --fix", + "lint": "eslint --ext .ts,.js src", + "lint:fix": "eslint --ext .ts,.js src --fix", "start": "node dist/" }, "keywords": [],