Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling sum rewards from all active plugins #3 #104

Draft
wants to merge 4 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"knip-ci": "knip --no-exit-code --reporter json",
"prepare": "husky install",
"test": "jest",
"build": "tsup"
"build": "tsup",
"start": "node dist/main.js"
},
"keywords": [
"typescript",
Expand Down
48 changes: 44 additions & 4 deletions src/generate-permits-from-context.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// src/generate-permits-from-context.ts

import * as github from "@actions/github";
import { Octokit } from "@octokit/rest";
import { Value } from "@sinclair/typebox/value";
import { createClient } from "@supabase/supabase-js";
import { createAdapters } from "./adapters";
import { Database } from "./adapters/supabase/types/database";
import { generatePayoutPermit } from "./handlers";
import { generateErc20Permit } from "./handlers/generate-erc20-permit";
import { registerWallet } from "./handlers/register-wallet";
import { Context } from "./types/context";
import { envSchema } from "./types/env";
import { permitGenerationSettingsSchema, PluginInputs } from "./types/plugin-input";
import { PluginInput, PluginOutput } from "./types/plugin-interfaces";

/**
* Generates all the permits based on the currently populated context.
Expand All @@ -27,6 +30,7 @@ export async function generatePermitsFromContext() {
authToken: webhookPayload.authToken,
ref: webhookPayload.ref,
};

const octokit = new Octokit({ auth: inputs.authToken });
const supabaseClient = createClient<Database>(env.SUPABASE_URL, env.SUPABASE_KEY);

Expand Down Expand Up @@ -61,14 +65,50 @@ export async function generatePermitsFromContext() {
if (context.eventName === "issue_comment.created") {
await handleSlashCommands(context, octokit);
} else {
const permits = await generatePayoutPermit(context, settings.permitRequests);
await returnDataToKernel(env.GITHUB_TOKEN, inputs.stateId, permits);
return JSON.stringify(permits);
const pluginInput: PluginInput = {
eventContext: {
eventName: inputs.eventName,
payload: inputs.eventPayload,
config: inputs.settings,
env,
},
inputValue: JSON.stringify(inputs.settings.permitRequests),
};

const erc20Output = await generateErc20Permit(pluginInput);

const pluginOutputs: PluginOutput[] = [erc20Output];

await aggregatePluginOutputs(pluginOutputs);
await returnDataToKernel(env.GITHUB_TOKEN, inputs.stateId, pluginOutputs);
return JSON.stringify(pluginOutputs);
}

return null;
}

async function aggregatePluginOutputs(pluginOutputs: PluginOutput[]): Promise<void> {
const totalRewards = pluginOutputs.flatMap(output => output.rewards);
const commentOutputs = pluginOutputs.map(output => output.commentOutput).join("\n");

// Sum the rewards and post a single comment
const totalRewardAmount = totalRewards.reduce((sum, reward) => sum + reward.amount, 0);
const finalComment = `<div>${commentOutputs}</div><p>Total Reward: ${totalRewardAmount}</p>`;

// Post the final comment
await postComment(finalComment);
}

async function postComment(comment: string): Promise<void> { //
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
await octokit.rest.issues.createComment({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: github.context.issue.number,
body: comment,
});
}

async function returnDataToKernel(repoToken: string, stateId: string, output: object) {
const octokit = new Octokit({ auth: repoToken });
await octokit.rest.repos.createDispatchEvent({
Expand Down
85 changes: 39 additions & 46 deletions src/handlers/generate-erc20-permit.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,55 @@
// src/handlers/generate-erc20-permit.ts

import { PluginInput, PluginOutput, Reward } from "../types/plugin-interfaces";
import { PERMIT2_ADDRESS, PermitTransferFrom, SignatureTransfer, MaxUint256 } from "@uniswap/permit2-sdk";
import { ethers, utils } from "ethers";
import { Context, Logger } from "../types/context";
import { PermitReward, TokenType } from "../types";
import { decrypt, parseDecryptedPrivateKey } from "../utils";
import { getFastestProvider } from "../utils/get-fastest-provider";

export interface Payload {
evmNetworkId: number;
evmPrivateEncrypted: string;
walletAddress: string;
issueNodeId: string;
logger: Logger;
userId: number;
}
export async function generateErc20Permit(input: PluginInput): Promise<PluginOutput> {
const { eventContext, inputValue, metadata } = input;
const { payload, config, env } = eventContext;

// Deserialize inputValue if needed
const { username, amount, tokenAddress } = JSON.parse(inputValue);

export async function generateErc20PermitSignature(payload: Payload, username: string, amount: number, tokenAddress: string): Promise<PermitReward>;
export async function generateErc20PermitSignature(context: Context, username: string, amount: number, tokenAddress: string): Promise<PermitReward>;
export async function generateErc20PermitSignature(
contextOrPayload: Context | Payload,
username: string,
amount: number,
tokenAddress: string
): Promise<PermitReward> {
let logger: Logger;
const _username = username;
let logger = metadata?.logger;
let walletAddress: string | null | undefined;
let issueNodeId: string;
let evmNetworkId: number;
let evmPrivateEncrypted: string;
let userId: number;

if ("issueNodeId" in contextOrPayload) {
logger = contextOrPayload.logger as Logger;
walletAddress = contextOrPayload.walletAddress;
evmNetworkId = contextOrPayload.evmNetworkId;
evmPrivateEncrypted = contextOrPayload.evmPrivateEncrypted;
issueNodeId = contextOrPayload.issueNodeId;
userId = contextOrPayload.userId;
if ("issueNodeId" in metadata) {
logger = metadata.logger as Logger;
walletAddress = metadata.walletAddress;
evmNetworkId = metadata.evmNetworkId;
evmPrivateEncrypted = metadata.evmPrivateEncrypted;
issueNodeId = metadata.issueNodeId;
userId = metadata.userId;
} else {
const config = contextOrPayload.config;
logger = contextOrPayload.logger;
const config = eventContext.config;
logger = metadata.logger;
const { evmNetworkId: configEvmNetworkId, evmPrivateEncrypted: configEvmPrivateEncrypted } = config;
const { data: userData } = await contextOrPayload.octokit.rest.users.getByUsername({ username: _username });
const { data: userData } = await metadata.octokit.rest.users.getByUsername({ username });
if (!userData) {
throw new Error(`GitHub user was not found for id ${_username}`);
throw new Error(`GitHub user was not found for id ${username}`);
}
userId = userData.id;
const { wallet } = contextOrPayload.adapters.supabase;
const { wallet } = metadata.adapters.supabase;
walletAddress = await wallet.getWalletByUserId(userId);
evmNetworkId = configEvmNetworkId;
evmPrivateEncrypted = configEvmPrivateEncrypted;
if ("issue" in contextOrPayload.payload) {
issueNodeId = contextOrPayload.payload.issue.node_id;
} else if ("pull_request" in contextOrPayload.payload) {
issueNodeId = contextOrPayload.payload.pull_request.node_id;
if ("issue" in payload) {
issueNodeId = payload.issue.node_id;
} else if ("pull_request" in payload) {
issueNodeId = payload.pull_request.node_id;
} else {
throw new Error("Issue Id is missing");
}
}

if (!_username) {
if (!username) {
throw new Error("User was not found");
}
if (!walletAddress) {
Expand Down Expand Up @@ -93,21 +83,24 @@ export async function generateErc20PermitSignature(
try {
const signature = await adminWallet._signTypedData(domain, types, values);

const erc20Permit: PermitReward = {
tokenType: TokenType.ERC20,
const erc20Permit: Reward = {
type: "ERC20",
amount: Number(permitTransferFromData.permitted.amount.toString()),
tokenAddress: permitTransferFromData.permitted.token,
beneficiary: permitTransferFromData.spender,
nonce: permitTransferFromData.nonce.toString(),
deadline: permitTransferFromData.deadline.toString(),
amount: permitTransferFromData.permitted.amount.toString(),
owner: adminWallet.address,
signature: signature,
networkId: evmNetworkId,
};

const commentOutput = `<p>Generated ERC20 permit for ${username} with amount ${amount} tokens.</p>`;

logger.info("Generated ERC20 permit2 signature", erc20Permit);

return erc20Permit;
return {
rewards: [erc20Permit],
commentOutput,
metadata: {
additionalInfo: "Example metadata",
},
};
} catch (error) {
logger.error(`Failed to sign typed data: ${error}`);
throw error;
Expand Down
23 changes: 23 additions & 0 deletions src/types/plugin-interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// plugin - interfaces

export interface PluginInput {
eventContext: EventContext;
inputValue: string; // selialized JSON or simple string parameters
metadata?: Record<string, unknown>; // optional meradata for additional context
}

export interface EventContext {
eventName: string;
payload: Record<string, unknown>;
config: record<strin, unknown>;
env: Record<string, string>;
}

export interface Reward {
type: "ERC20" | "ERC721";
amount: number;
tokenAddress: string;
beneficiary: string;
}


Loading