From d6bb8473610f12cd0f9237032a9fdd0c01c68051 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Wed, 19 Feb 2025 14:12:01 +0900 Subject: [PATCH] fix: removed provider permit generation --- package.json | 4 +- src/helpers/permit.ts | 129 +++++++++++++++++++++++++ src/parser/permit-generation-module.ts | 36 +++---- 3 files changed, 146 insertions(+), 23 deletions(-) create mode 100644 src/helpers/permit.ts diff --git a/package.json b/package.json index bd51bf31..46fea838 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "js-tiktoken": "1.0.15", "jsdom": "24.0.0", "markdown-it": "14.1.0", - "ms": "^2.1.3", "minimatch": "^10.0.1", + "ms": "^2.1.3", "openai": "4.56.0", "yaml": "^2.6.1" }, @@ -81,8 +81,8 @@ "prettier": "3.3.3", "sqlite": "^5.1.1", "sqlite3": "^5.1.7", - "ts-node": "10.9.2", "ts-jest": "^29.2.5", + "ts-node": "10.9.2", "typescript": "^5.6.3", "typescript-eslint": "^8.13.0" }, diff --git a/src/helpers/permit.ts b/src/helpers/permit.ts new file mode 100644 index 00000000..41486d7b --- /dev/null +++ b/src/helpers/permit.ts @@ -0,0 +1,129 @@ +import { createAdapters, decrypt, parseDecryptedPrivateKey } from "@ubiquity-os/permit-generation"; +import { ethers, utils } from "ethers"; +import { ContextPlugin } from "../types/plugin-input"; + +type PermitDetails = { + token: string; // address + amount: ethers.BigNumberish; // uint160 + expiration: string; // uint48 + nonce: ethers.BigNumberish; // uint48 +}; + +type PermitSingle = { + details: PermitDetails; + spender: string; // address + sigDeadline: ethers.BigNumberish; // uint256 +}; + +// These are the EIP-712 type definitions from Uniswap's Permit2. +const permit2Types = { + PermitSingle: [ + { name: "details", type: "PermitDetails" }, + { name: "spender", type: "address" }, + { name: "sigDeadline", type: "uint256" }, + ], + PermitDetails: [ + { name: "token", type: "address" }, + { name: "amount", type: "uint160" }, + { name: "expiration", type: "uint48" }, + { name: "nonce", type: "uint48" }, + ], +}; + +/** + * signPermit2() generates an EIP-712 signature for Uniswap's Permit2. + * + * @param privateKey the private key of the signer + * @param permit2Address the deployed Permit2 contract address + * @param chainId the chain ID + * @param permitSingle the permit data + * @returns the signature string + */ +export async function signPermit2( + privateKey: string, + permit2Address: string, + chainId: number, + permitSingle: PermitSingle +): Promise { + const domainData = { + name: "Permit2", + version: "1", + chainId, + verifyingContract: permit2Address, + }; + + const wallet = new ethers.Wallet(privateKey); + return wallet._signTypedData(domainData, permit2Types, permitSingle); +} + +async function getPrivateKey(evmPrivateEncrypted: string) { + try { + const privateKeyDecrypted = await decrypt(evmPrivateEncrypted, String(process.env.X25519_PRIVATE_KEY)); + const privateKeyParsed = parseDecryptedPrivateKey(privateKeyDecrypted); + const privateKey = privateKeyParsed.privateKey; + if (!privateKey) throw new Error("Private key is not defined"); + return privateKey; + } catch (error) { + const errorMessage = `Failed to decrypt a private key: ${error}`; + throw new Error(errorMessage); + } +} + +/** + * Generates a claim base64 encoded compatible with pay.ubq.fi + */ +export async function generatePermitUrlPayload( + context: ContextPlugin, + userName: string, + chainId: number, + amount: number, + erc20RewardToken: string, + adapters: ReturnType +) { + const privateKey = await getPrivateKey(context.config.evmPrivateEncrypted); // Replace with your private key + const permit2Address = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // Deployed Permit2 contract + const convertedAmount = utils.parseUnits(amount.toString(), 18); + const deadline = Date.now().toString(); + const spenderWallet = new ethers.Wallet(privateKey); + const { data: userData } = await context.octokit.rest.users.getByUsername({ username: userName }); + + if (!userData) { + throw new Error(`GitHub user was not found for id ${userName}`); + } + + // Had to truncate the nonce to fit in an uint48 + const nonce = + BigInt(utils.keccak256(utils.toUtf8Bytes(`${userData.id}-${context.payload.issue.node_id}`))) % BigInt(2 ** 48); + const walletAddress = await adapters.supabase.wallet.getWalletByUserId(userData.id); + const permitSingle: PermitSingle = { + details: { + token: erc20RewardToken, + amount: convertedAmount, // 1 token with 18 decimals + expiration: deadline, // Unix timestamp + nonce: nonce.toString(), + }, + spender: spenderWallet.address, + sigDeadline: deadline, // Unix timestamp + }; + const signature = await signPermit2(privateKey, permit2Address, chainId, permitSingle); + const permit = { + type: "erc20-permit", + permit: { + permitted: { + token: erc20RewardToken, + amount: convertedAmount, + }, + nonce: nonce.toString(), + deadline: deadline, + }, + transferDetails: { + to: walletAddress, + requestedAmount: convertedAmount, + }, + owner: spenderWallet.address, + signature: signature, + networkId: chainId, + }; + + return Buffer.from(JSON.stringify([permit])).toString("base64"); +} diff --git a/src/parser/permit-generation-module.ts b/src/parser/permit-generation-module.ts index 1e213344..6691187b 100644 --- a/src/parser/permit-generation-module.ts +++ b/src/parser/permit-generation-module.ts @@ -7,8 +7,6 @@ import { createAdapters, Database, decrypt, - encodePermits, - generatePayoutPermit, parseDecryptedPrivateKey, PermitReward, SupportedEvents, @@ -19,12 +17,13 @@ import { PermitGenerationConfiguration, permitGenerationConfigurationType, } from "../configuration/permit-generation-configuration"; +import { isAdmin, isCollaborative } from "../helpers/checkers"; +import { generatePermitUrlPayload } from "../helpers/permit"; import { IssueActivity } from "../issue-activity"; import { getRepo, parseGitHubUrl } from "../start"; import { EnvConfig } from "../types/env-type"; import { BaseModule } from "../types/module"; import { Result } from "../types/results"; -import { isAdmin, isCollaborative } from "../helpers/checkers"; interface Payload { evmNetworkId: number; @@ -110,28 +109,23 @@ export class PermitGenerationModule extends BaseModule { }, ], }; - const permits = await generatePayoutPermit( - { + result[key].permitUrl = `https://pay.ubq.fi?claim=${await generatePermitUrlPayload( + this.context, + key, + payload.evmNetworkId, + value.total, + payload.erc20RewardToken, + createAdapters(this._supabase, { 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); + logger: permitLogger, + payload, + adapters, + }) + )}`; + // 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 }); }