Skip to content

Commit

Permalink
feat: add the payment module
Browse files Browse the repository at this point in the history
  • Loading branch information
hhio618 committed Jan 7, 2025
1 parent b757f45 commit c7d396e
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 48 deletions.
40 changes: 39 additions & 1 deletion src/helpers/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,45 @@ export async function getFundingWalletBalance(networkId: number, tokenAddress: s
const handler = new RPCHandler(config);
const provider = await handler.getFastestRpcProvider();

// fetch token symbol
// fetch token balance
const contract = new ethers.Contract(tokenAddress, abi, provider);
return await contract.balanceOf(fundingWalledAddress);
}

/**
* Returns Transaction for the ERC20 token transfer
* @param networkId Network id
* @param tokenAddress ERC20 token address
* @param _evmPrivateEncrypted encrypted private key of the funding wallet address
* @param to reciever address
* @param amount Amount of ERC20 token to be transfered
* @returns Transaction for the ERC20 token transfer
*/
export async function transferTo(networkId: number, tokenAddress: string, _evmPrivateEncrypted: string, to: string, amount: string) {
const abi = ["function transfer(address,uint256) public returns (bool)"];

// 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();

// send the transaction
// TODO: sign the transaction
const contract = new ethers.Contract(tokenAddress, abi, provider);
return await contract.transfer(to, amount);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface Payload {
issue: { node_id: string };
}

export class PermitGenerationModule extends BaseModule {
export class PaymentModule extends BaseModule {
readonly _configuration: PermitGenerationConfiguration | null = this.context.config.incentives.permitGeneration;
readonly _autoTransferMode: boolean = this.context.config.automaticTransferMode;
readonly _fundingWalletAddress: string = this.context.config.fundingWalletAddress;
Expand All @@ -44,10 +44,10 @@ export class PermitGenerationModule extends BaseModule {
readonly _supabase = createClient<Database>(this.context.env.SUPABASE_URL, this.context.env.SUPABASE_KEY);

async transform(data: Readonly<IssueActivity>, result: Result): Promise<Result> {
const canGeneratePermits = await this._canGeneratePermit(data);
const canMakePayment = await this._canMakePayment(data);

if (!canGeneratePermits) {
this.context.logger.error("[PermitGenerationModule] Non collaborative issue detected, skipping.");
if (!canMakePayment) {
this.context.logger.error("[PaymentModule] Non collaborative issue detected, skipping.");
return Promise.resolve(result);
}

Expand All @@ -58,12 +58,13 @@ export class PermitGenerationModule extends BaseModule {
this._fundingWalletAddress
);

let shouldTransferDirectly = false;
if (this._autoTransferMode && sumPayouts < fundingWalletBalance) {
this.context.logger.debug(
"[PermitGenerationModule] AutoTransformMode is enabled, " +
"and sufficient funds are available in the funding wallet, skipping."
"[PaymentModule] AutoTransformMode is enabled, " +
"and sufficient funds are available in the funding wallet, skipping."
);
return Promise.resolve(result);
shouldTransferDirectly = true;
}

const payload: Context["payload"] & Payload = {
Expand All @@ -86,7 +87,7 @@ export class PermitGenerationModule extends BaseModule {
);
if (!isPrivateKeyAllowed) {
this.context.logger.error(
"[PermitGenerationModule] Private key is not allowed to be used in this organization/repository."
"[PaymentModule] Private key is not allowed to be used in this organization/repository."
);
return Promise.resolve(result);
}
Expand Down Expand Up @@ -117,21 +118,23 @@ export class PermitGenerationModule extends BaseModule {

for (const [key, value] of Object.entries(result)) {
this.context.logger.debug(`Updating result for user ${key}`);
try {
const config: Context["config"] = {
evmNetworkId: payload.evmNetworkId,
evmPrivateEncrypted: payload.evmPrivateEncrypted,
permitRequests: [
{
amount: value.total,
username: key,
contributionType: "",
type: TokenType.ERC20,
tokenAddress: payload.erc20RewardToken,
},
],
};
const permits = await generatePayoutPermit(
const config: Context["config"] = {
evmNetworkId: payload.evmNetworkId,
evmPrivateEncrypted: payload.evmPrivateEncrypted,
permitRequests: [
{
amount: value.total,
username: key,
contributionType: "",
type: TokenType.ERC20,
tokenAddress: payload.erc20RewardToken,
},
],
};

if (shouldTransferDirectly) {

const txid = await transferErc20(
{
env,
eventName,
Expand All @@ -151,17 +154,56 @@ export class PermitGenerationModule extends BaseModule {
},
config.permitRequests
);
result[key].permitUrl = `https://pay.ubq.fi?claim=${encodePermits(permits)}`;
await this._savePermitsToDatabase(result[key].userId, { issueUrl: payload.issueUrl, issueId }, permits);
} catch (e) {
this.context.logger.error(`[PermitGenerationModule] Failed to generate permits for user ${key}`, { e });
result[key].permitUrl = `https://gnosisscan.io/tx/${txid}`;
//TODO: We need to save this record in the database
} else {
try {
const permits = await generatePayoutPermit(
{
env,
eventName,
logger: permitLogger,
payload,
adapters: createAdapters(this._supabase, {
env,
eventName,
octokit,
config,
logger: permitLogger,
payload,
adapters,
}),
octokit,
config,
},
config.permitRequests
);
result[key].permitUrl = `https://pay.ubq.fi?claim=${encodePermits(permits)}`;
await this._savePermitsToDatabase(result[key].userId, { issueUrl: payload.issueUrl, issueId }, permits);
// remove treasury item from final result in order not to display permit fee in GitHub comments
if (env.PERMIT_TREASURY_GITHUB_USERNAME) delete result[env.PERMIT_TREASURY_GITHUB_USERNAME];
} catch (e) {
this.context.logger.error(`[PaymentModule] Failed to generate permits for user ${key}`, { e });
}
}
}
return result;
}

// remove treasury item from final result in order not to display permit fee in GitHub comments
if (env.PERMIT_TREASURY_GITHUB_USERNAME) delete result[env.PERMIT_TREASURY_GITHUB_USERNAME];
async _sumPayouts(result: Result) {
let sumPayouts = 0;
for (const value of Object.values(result)) {
sumPayouts += value.total;
}
return sumPayouts;
}

return result;
async _canMakePayment(data: Readonly<IssueActivity>) {
if (!data.self?.closed_by || !data.self.user) return false;

if (await isAdmin(data.self.user.login, this.context)) return true;

return isCollaborative(data);
}

/**
Expand Down Expand Up @@ -220,21 +262,7 @@ export class PermitGenerationModule extends BaseModule {
return this._deductFeeFromReward(result, treasuryGithubData);
}

async _canGeneratePermit(data: Readonly<IssueActivity>) {
if (!data.self?.closed_by || !data.self.user) return false;

if (await isAdmin(data.self.user.login, this.context)) return true;

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,
Expand Down Expand Up @@ -417,7 +445,7 @@ export class PermitGenerationModule extends BaseModule {

get enabled(): boolean {
if (!Value.Check(permitGenerationConfigurationType, this._configuration)) {
this.context.logger.error("Invalid / missing configuration detected for PermitGenerationModule, disabling.");
this.context.logger.error("Invalid / missing configuration detected for PaymentModule, disabling.");
return false;
}
return true;
Expand Down
4 changes: 2 additions & 2 deletions src/parser/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ContentEvaluatorModule } from "./content-evaluator-module";
import { DataPurgeModule } from "./data-purge-module";
import { FormattingEvaluatorModule } from "./formatting-evaluator-module";
import { GithubCommentModule } from "./github-comment-module";
import { PermitGenerationModule } from "./permit-generation-module";
import { PaymentModule } from "./payment-module";
import { UserExtractorModule } from "./user-extractor-module";
import { getTaskReward } from "../helpers/label-price-extractor";
import { GitHubIssue } from "../github-types";
Expand All @@ -25,7 +25,7 @@ export class Processor {
.add(new DataPurgeModule(context))
.add(new FormattingEvaluatorModule(context))
.add(new ContentEvaluatorModule(context))
.add(new PermitGenerationModule(context))
.add(new PaymentModule(context))
.add(new GithubCommentModule(context));
this._context = context;
this._configuration = this._context.config.incentives;
Expand Down
1 change: 1 addition & 0 deletions src/types/results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface Result {
};
feeRate?: number;
permitUrl?: string;
explorerUrl?: string;
userId: number;
evaluationCommentHtml?: string;
};
Expand Down

0 comments on commit c7d396e

Please sign in to comment.