From 03fe0ee86577062e181198b3b160a5fb7d7f50cc Mon Sep 17 00:00:00 2001 From: hh Date: Tue, 7 Jan 2025 18:01:44 +0330 Subject: [PATCH] feat: add auto transfer configs --- src/helpers/web3.ts | 35 ++++++++++++++++++++++++++ src/parser/permit-generation-module.ts | 28 +++++++++++++++++++++ src/types/plugin-input.ts | 18 ++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/helpers/web3.ts b/src/helpers/web3.ts index e7f8febc..87b2884e 100644 --- a/src/helpers/web3.ts +++ b/src/helpers/web3.ts @@ -34,3 +34,38 @@ export async function getErc20TokenSymbol(networkId: number, tokenAddress: strin const contract = new ethers.Contract(tokenAddress, abi, provider); return await contract.symbol(); } + +/** + * Returns ERC20 token balance of the funding wallet + * @param networkId Network id + * @param tokenAddress ERC20 token address + * @param fundingWalledAddress funding wallet address + * @returns ERC20 token balance of the funding wallet + */ +export async function getFundingWalletBalance(networkId: number, tokenAddress: string, fundingWalledAddress: string) { + const abi = ["function balanceOf(address) view returns (uint256)"]; + + // get fastest RPC + const config: HandlerConstructorConfig = { + networkName: null, + networkRpcs: null, + proxySettings: { + retryCount: 5, + retryDelay: 500, + logTier: null, + logger: null, + strictLogs: false, + }, + runtimeRpcs: null, + networkId: String(networkId) as NetworkId, + rpcTimeout: 1500, + autoStorage: false, + cacheRefreshCycles: 10, + }; + const handler = new RPCHandler(config); + const provider = await handler.getFastestRpcProvider(); + + // fetch token symbol + const contract = new ethers.Contract(tokenAddress, abi, provider); + return await contract.balanceOf(fundingWalledAddress); +} diff --git a/src/parser/permit-generation-module.ts b/src/parser/permit-generation-module.ts index 1e213344..f2830735 100644 --- a/src/parser/permit-generation-module.ts +++ b/src/parser/permit-generation-module.ts @@ -25,6 +25,7 @@ import { EnvConfig } from "../types/env-type"; import { BaseModule } from "../types/module"; import { Result } from "../types/results"; import { isAdmin, isCollaborative } from "../helpers/checkers"; +import { getFundingWalletBalance } from "../helpers/web3"; interface Payload { evmNetworkId: number; @@ -36,6 +37,10 @@ interface Payload { export class PermitGenerationModule extends BaseModule { readonly _configuration: PermitGenerationConfiguration | null = this.context.config.incentives.permitGeneration; + readonly _autoTransferMode: boolean = this.context.config.automaticTransferMode; + readonly _fudningWalletAddress: string = this.context.config.fundingWalletAddress; + readonly _evmNetworkId: number = this.context.config.evmNetworkId; + readonly _erc20RewardToken: string = this.context.config.erc20RewardToken; readonly _supabase = createClient(this.context.env.SUPABASE_URL, this.context.env.SUPABASE_KEY); async transform(data: Readonly, result: Result): Promise { @@ -45,6 +50,21 @@ export class PermitGenerationModule extends BaseModule { this.context.logger.error("[PermitGenerationModule] Non collaborative issue detected, skipping."); return Promise.resolve(result); } + + const sumPayouts = await this._sumPayouts(result); + const fundingWalletBalance = await getFundingWalletBalance( + this._evmNetworkId, + this._erc20RewardToken, + this._fudningWalletAddress + ); + + if (this._autoTransferMode && sumPayouts < fundingWalletBalance) { + this.context.logger.debug( + "[PermitGenerationModule] AutoTransforMode is enabled and there are enough funds, skipping." + ); + return Promise.resolve(result); + } + const payload: Context["payload"] & Payload = { ...context.payload.inputs, issueUrl: this.context.payload.issue.html_url, @@ -207,6 +227,14 @@ export class PermitGenerationModule extends BaseModule { return isCollaborative(data); } + async _sumPayouts(result: Result) { + let sumPayouts = 0; + for (const value of Object.values(result)) { + sumPayouts += value.total; + } + return sumPayouts; + } + _deductFeeFromReward( result: Result, treasuryGithubData: RestEndpointMethodTypes["users"]["getByUsername"]["response"]["data"] diff --git a/src/types/plugin-input.ts b/src/types/plugin-input.ts index 1ac7d5ee..4344e6da 100644 --- a/src/types/plugin-input.ts +++ b/src/types/plugin-input.ts @@ -19,13 +19,29 @@ export const pluginSettingsSchema = T.Object( * The encrypted key to use for permit generation */ evmPrivateEncrypted: T.String({ - description: "The encrypted key to use for permit generation", + description: "The encrypted key to use for permit generation and auto transfers", + examples: ["0x000..."], + }), + /** + * Funding wallet address (corresponding to evmPrivateEncrypted) + */ + fundingWalletAddress: T.String({ + description: "The funding wallet address", examples: ["0x000..."], }), /** * Reward token for ERC20 permits, default WXDAI for gnosis chain */ erc20RewardToken: T.String({ default: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d" }), + /** + * If set to false or if there are insufficient funds to settle the payment, + * permits will be generated instead of processing direct payouts. + */ + automaticTransferMode: T.Boolean({ + default: true, + description: + "If set to false or if there are insufficient funds to settle the payment, permits will be generated instead of processing direct payouts.", + }), incentives: T.Object( { /**