Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #485 from web4er/register-wallet-config
Browse files Browse the repository at this point in the history
Add config param to enable/disable wallet signature
  • Loading branch information
0xcodercrane authored Jul 31, 2023
2 parents 0ec8b61 + e5357eb commit 8779299
Show file tree
Hide file tree
Showing 16 changed files with 117 additions and 86 deletions.
2 changes: 1 addition & 1 deletion .github/ubiquibot-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ auto-pay-mode: true
comment-incentives: true
max-concurrent-bounties: 2
promotion-comment: "\n<h6>If you enjoy the DevPool experience, please follow <a href='https://github.com/ubiquity'>Ubiquity on GitHub</a> and star <a href='https://github.com/ubiquity/devpool-directory'>this repo</a> to show your support. It helps a lot!</h6>"

register-wallet-with-verification: false
9 changes: 4 additions & 5 deletions src/adapters/supabase/helpers/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,11 @@ export const getWalletMultiplier = async (username: string): Promise<number> =>

export const getWalletInfo = async (username: string): Promise<{ multiplier: number | null; address: string | null } | number | undefined> => {
const { supabase } = getAdapters();

const { data } = await supabase.from('wallets').select('multiplier, address').eq("user_name", username).single();
if (data?.multiplier == null || data?.address == null) return 1
else return {multiplier: data?.multiplier, address: data?.address}
};

const { data } = await supabase.from("wallets").select("multiplier, address").eq("user_name", username).single();
if (data?.multiplier == null || data?.address == null) return 1;
else return { multiplier: data?.multiplier, address: data?.address };
};

export const getMultiplierReason = async (username: string): Promise<string> => {
const { supabase } = getAdapters();
Expand Down
4 changes: 4 additions & 0 deletions src/bindings/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const loadConfig = async (context: Context): Promise<BotConfig> => {
issueCreatorMultiplier,
defaultLabels,
promotionComment,
registerWalletWithVerification,
} = await getWideConfig(context);

const publicKey = await getScalarKey(process.env.X25519_PRIVATE_KEY);
Expand Down Expand Up @@ -74,6 +75,9 @@ export const loadConfig = async (context: Context): Promise<BotConfig> => {
privateKey: process.env.X25519_PRIVATE_KEY ?? "",
publicKey: publicKey ?? "",
},
wallet: {
registerWalletWithVerification: registerWalletWithVerification,
},
};

if (botConfig.log.ingestionKey == "") {
Expand Down
1 change: 1 addition & 0 deletions src/bindings/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const bindEvents = async (context: Context): Promise<void> => {
unassign: botConfig.unassign,
mode: botConfig.mode,
log: botConfig.log,
wallet: botConfig.wallet,
})}`
);
const allowedEvents = Object.values(GithubEvent) as string[];
Expand Down
3 changes: 0 additions & 3 deletions src/configs/shared.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { generateHelpMenu } from "../handlers";

export const COLORS = {
default: "ededed",
price: "1f883d",
Expand Down Expand Up @@ -28,4 +26,3 @@ export const DEFAULT_DISQUALIFY_TIME = "7 days"; // 7 days
export const DEFAULT_NETWORK_ID = 1; // ethereum
export const DEFAULT_RPC_ENDPOINT = "https://rpc-bot.ubq.fi/v1/mainnet";
export const DEFAULT_PERMIT_BASE_URL = "https://pay.ubq.fi";
export const COMMAND_INSTRUCTIONS = generateHelpMenu();
9 changes: 5 additions & 4 deletions src/handlers/comment/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ export const handleComment = async (): Promise<void> => {
}

const body = comment.body;
const commands = commentParser(body);
const commentedCommands = commentParser(body);

if (commands.length === 0) {
if (commentedCommands.length === 0) {
await verifyFirstCheck();
return;
}

for (const command of commands) {
const userCommand = userCommands.find((i) => i.id == command);
const allCommands = userCommands();
for (const command of commentedCommands) {
const userCommand = allCommands.find((i) => i.id == command);

if (userCommand) {
const { handler, callback, successComment, failureComment } = userCommand;
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/comment/handlers/assign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const assign = async (body: string) => {
const endTime = new Date(startTime + duration * 1000);

const comment = {
deadline: endTime.toUTCString().replace("GMT","UTC"),
deadline: endTime.toUTCString().replace("GMT", "UTC"),
wallet: (await getWalletAddress(payload.sender.login)) || "Please set your wallet address to use `/wallet 0x0000...0000`",
multiplier: "1.00",
reason: await getMultiplierReason(payload.sender.login),
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/comment/handlers/first.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getBotContext, getLogger } from "../../../bindings";
import { COMMAND_INSTRUCTIONS } from "../../../configs";
import { upsertCommentToIssue } from "../../../helpers";
import { Payload } from "../../../types";
import { generateHelpMenu } from "./help";

export const verifyFirstCheck = async (): Promise<void> => {
const context = getBotContext();
Expand All @@ -25,7 +25,7 @@ export const verifyFirstCheck = async (): Promise<void> => {
const isFirstComment = resp.data.filter((item) => item.user?.login === payload.sender.login).length === 1;
if (isFirstComment) {
//first_comment
const msg = `${COMMAND_INSTRUCTIONS}\n@${payload.sender.login}`;
const msg = `${generateHelpMenu()}\n@${payload.sender.login}`;
await upsertCommentToIssue(payload.issue.number, msg, payload.action, payload.comment);
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/handlers/comment/handlers/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ export const listAvailableCommands = async (body: string) => {

export const generateHelpMenu = () => {
let helpMenu = "### Available commands\n```";

userCommands.map((command) => {
const commands = userCommands();
commands.map((command) => {
// if first command, add a new line
if (command.id === userCommands[0].id) {
if (command.id === commands[0].id) {
helpMenu += `\n`;
if (!ASSIGN_COMMAND_ENABLED) return;
}
helpMenu += `- ${command.id}: ${command.description}`;
// if not last command, add a new line (fixes too much space below)
if (command.id !== userCommands[userCommands.length - 1].id) {
if (command.id !== commands[commands.length - 1].id) {
helpMenu += `\n`;
}
});
Expand Down
101 changes: 53 additions & 48 deletions src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getBotConfig } from "../../../bindings";
import { Comment, Payload, UserCommands } from "../../../types";
import { IssueCommentCommands } from "../commands";
import { assign } from "./assign";
Expand All @@ -10,7 +9,7 @@ import { registerWallet } from "./wallet";
import { setAccess } from "./allow";
import { multiplier } from "./multiplier";
import { addCommentToIssue, createLabel, addLabelToIssue, getLabel, upsertCommentToIssue } from "../../../helpers";
import { getBotContext } from "../../../bindings";
import { getBotConfig, getBotContext } from "../../../bindings";
import { handleIssueClosed } from "../../payout";
import { query } from "./query";

Expand Down Expand Up @@ -99,54 +98,60 @@ const commandCallback = async (issue_number: number, comment: string, action: st
await upsertCommentToIssue(issue_number, comment, action, reply_to);
};

export const userCommands: UserCommands[] = [
{
id: IssueCommentCommands.ASSIGN,
description: "Assign the origin sender to the issue automatically.",
handler: assign,
callback: commandCallback,
},
{
id: IssueCommentCommands.UNASSIGN,
description: "Unassign the origin sender from the issue automatically.",
handler: unassign,
callback: commandCallback,
},
{
handler: listAvailableCommands,
id: IssueCommentCommands.HELP,
description: "List all available commands.",
callback: commandCallback,
},
// Commented out until Gnosis Safe is integrated (https://github.com/ubiquity/ubiquibot/issues/353)
/*{
export const userCommands = (): UserCommands[] => {
const config = getBotConfig();

return [
{
id: IssueCommentCommands.ASSIGN,
description: "Assign the origin sender to the issue automatically.",
handler: assign,
callback: commandCallback,
},
{
id: IssueCommentCommands.UNASSIGN,
description: "Unassign the origin sender from the issue automatically.",
handler: unassign,
callback: commandCallback,
},
{
handler: listAvailableCommands,
id: IssueCommentCommands.HELP,
description: "List all available commands.",
callback: commandCallback,
},
// Commented out until Gnosis Safe is integrated (https://github.com/ubiquity/ubiquibot/issues/353)
/*{
id: IssueCommentCommands.PAYOUT,
description: "Disable automatic payment for the issue.",
handler: payout,
callback: commandCallback,
},*/
{
id: IssueCommentCommands.QUERY,
description: `Comments the users multiplier and address`,
handler: query,
callback: commandCallback,
},
{
id: IssueCommentCommands.MULTIPLIER,
description: `Set the bounty payout multiplier for a specific contributor, and provide the reason for why. \n example usage: "/wallet @user 0.5 'Multiplier reason'"`,
handler: multiplier,
callback: commandCallback,
},
{
id: IssueCommentCommands.ALLOW,
description: `Set access control. (Admin Only)`,
handler: setAccess,
callback: commandCallback,
},
{
id: IssueCommentCommands.WALLET,
description: `<WALLET_ADDRESS | ENS_NAME> <SIGNATURE_HASH>: Register the hunter's wallet address. \n Your message to sign is: DevPool\n You can generate SIGNATURE_HASH at https://etherscan.io/verifiedSignatures\n ex1: /wallet 0x0000000000000000000000000000000000000000 0xe2a3e34a63f3def2c29605de82225b79e1398190b542be917ef88a8e93ff9dc91bdc3ef9b12ed711550f6d2cbbb50671aa3f14a665b709ec391f3e603d0899a41b\n`,
handler: registerWallet,
callback: commandCallback,
},
];
{
id: IssueCommentCommands.QUERY,
description: `Comments the users multiplier and address`,
handler: query,
callback: commandCallback,
},
{
id: IssueCommentCommands.MULTIPLIER,
description: `Set the bounty payout multiplier for a specific contributor, and provide the reason for why. \n example usage: "/wallet @user 0.5 'Multiplier reason'"`,
handler: multiplier,
callback: commandCallback,
},
{
id: IssueCommentCommands.ALLOW,
description: `Set access control. (Admin Only)`,
handler: setAccess,
callback: commandCallback,
},
{
id: IssueCommentCommands.WALLET,
description: config.wallet.registerWalletWithVerification
? `<WALLET_ADDRESS | ENS_NAME> <SIGNATURE_HASH>: Register the hunter's wallet address. \n Your message to sign is: DevPool\n You can generate SIGNATURE_HASH at https://etherscan.io/verifiedSignatures\n ex1: /wallet 0x0000000000000000000000000000000000000000 0xe2a3e34a63f3def2c29605de82225b79e1398190b542be917ef88a8e93ff9dc91bdc3ef9b12ed711550f6d2cbbb50671aa3f14a665b709ec391f3e603d0899a41b\n ex2: /wallet vitalik.eth 0x75329f883590507e581cd6dfca62680b6cd12e1f1665db8097f9e642ed70025146b5cf9f777dde90c4a9cbd41500a6bf76bc394fd0b0cae2aab09f7a6f30e3b31b\n`
: `<WALLET_ADDRESS | ENS_NAME>: Register the hunter's wallet address. \n ex1: /wallet 0x0000000000000000000000000000000000000000\n ex2: /wallet vitalik.eth\n`,
handler: registerWallet,
callback: commandCallback,
},
];
};
36 changes: 21 additions & 15 deletions src/handlers/comment/handlers/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ethers } from "ethers";
import { upsertWalletAddress } from "../../../adapters/supabase";
import { getBotContext, getLogger } from "../../../bindings";
import { getBotConfig, getBotContext, getLogger } from "../../../bindings";
import { resolveAddress } from "../../../helpers";
import { Payload } from "../../../types";
import { formatEthAddress } from "../../../utils";
Expand All @@ -24,6 +24,7 @@ const extractEnsName = (text: string): string | undefined => {

export const registerWallet = async (body: string) => {
const { payload: _payload } = getBotContext();
const config = getBotConfig();
const logger = getLogger();
const payload = _payload as Payload;
const sender = payload.sender.login;
Expand All @@ -35,6 +36,9 @@ export const registerWallet = async (body: string) => {

if (!address && !ensName) {
logger.info("Skipping to register a wallet address because both address/ens doesn't exist");
if (config.wallet.registerWalletWithVerification) {
return `Please include your wallet or ENS address.\n usage: /wallet 0x0000000000000000000000000000000000000000 0x0830f316c982a7fd4ff050c8fdc1212a8fd92f6bb42b2337b839f2b4e156f05a359ef8f4acd0b57cdedec7874a865ee07076ab2c81dc9f9de28ced55228587f81c`;
}
return `Please include your wallet or ENS address.\n usage: /wallet 0x0000000000000000000000000000000000000000`;
}

Expand All @@ -48,24 +52,26 @@ export const registerWallet = async (body: string) => {
logger.info(`Resolved address from Ens name: ${ensName}, address: ${address}`);
}

const regexForSigHash = /(0x[a-fA-F0-9]{130})/g;
const sigHashMatches = body.match(regexForSigHash);
const sigHash = sigHashMatches ? sigHashMatches[0] : null;
if (config.wallet.registerWalletWithVerification) {
const regexForSigHash = /(0x[a-fA-F0-9]{130})/g;
const sigHashMatches = body.match(regexForSigHash);
const sigHash = sigHashMatches ? sigHashMatches[0] : null;

const messageToSign = "DevPool";
const failedSigLogMsg = `Skipping to register the wallet address because you have not provided a valid SIGNATURE_HASH.`;
const failedSigResponse = `Skipping to register the wallet address because you have not provided a valid SIGNATURE_HASH. \nUse [etherscan](https://etherscan.io/verifiedSignatures) to sign the message \`${messageToSign}\` and register your wallet by appending the signature hash.\n\n**Usage:**\n/wallet <WALLET_ADDRESS | ENS_NAME> <SIGNATURE_HASH>\n\n**Example:**\n/wallet 0x16ce4d863eD687455137576da2A0cbaf4f1E8f76 0x0830f316c982a7fd4ff050c8fdc1212a8fd92f6bb42b2337b839f2b4e156f05a359ef8f4acd0b57cdedec7874a865ee07076ab2c81dc9f9de28ced55228587f81c`;
try {
//verifyMessage throws an error when some parts(r,s,v) of the signature are correct but some are not
const isSigHashValid = address && sigHash && ethers.utils.verifyMessage(messageToSign, sigHash) == ethers.utils.getAddress(address);
if (!isSigHashValid) {
const messageToSign = "DevPool";
const failedSigLogMsg = `Skipping to register the wallet address because you have not provided a valid SIGNATURE_HASH.`;
const failedSigResponse = `Skipping to register the wallet address because you have not provided a valid SIGNATURE_HASH. \nUse [etherscan](https://etherscan.io/verifiedSignatures) to sign the message \`${messageToSign}\` and register your wallet by appending the signature hash.\n\n**Usage:**\n/wallet <WALLET_ADDRESS | ENS_NAME> <SIGNATURE_HASH>\n\n**Example:**\n/wallet 0x0000000000000000000000000000000000000000 0x0830f316c982a7fd4ff050c8fdc1212a8fd92f6bb42b2337b839f2b4e156f05a359ef8f4acd0b57cdedec7874a865ee07076ab2c81dc9f9de28ced55228587f81c`;
try {
//verifyMessage throws an error when some parts(r,s,v) of the signature are correct but some are not
const isSigHashValid = address && sigHash && ethers.utils.verifyMessage(messageToSign, sigHash) == ethers.utils.getAddress(address);
if (!isSigHashValid) {
logger.info(failedSigLogMsg);
return failedSigResponse;
}
} catch (e) {
logger.info(`Exception thrown by verifyMessage for /wallet: ${e}`);
logger.info(failedSigLogMsg);
return failedSigResponse;
}
} catch (e) {
logger.info(`Exception thrown by verifyMessage for /wallet: ${e}`);
logger.info(failedSigLogMsg);
return failedSigResponse;
}

if (address) {
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/payout/action.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getWalletAddress, getWalletMultiplier } from "../../adapters/supabase";
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import { addLabelToIssue, deleteLabel, generatePermit2Signature, getAllIssueComments, getTokenSymbol } from "../../helpers";
import {UserType, Payload, StateReason } from "../../types";
import { UserType, Payload, StateReason } from "../../types";
import { shortenEthAddress } from "../../utils";
import { bountyInfo } from "../wildcard";

Expand Down Expand Up @@ -67,7 +67,7 @@ export const handleIssueClosed = async () => {
const comment = `### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```";
const comments = await getAllIssueComments(issue.number);
const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot);
if (permitComments.length > 0) {
if (permitComments.length > 0) {
logger.info(`Skip to generate a permit url because it has been already posted`);
return `Permit generation skipped because it was already posted to this issue.`;
}
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,4 +507,4 @@ export const getAvailableOpenedPullRequests = async (username: string) => {
}
}
return result;
};
};
5 changes: 5 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export const CommentsSchema = Type.Object({
promotionComment: Type.String(),
});

export const WalletSchema = Type.Object({
registerWalletWithVerification: Type.Boolean(),
});

export const BotConfigSchema = Type.Object({
log: LogConfigSchema,
price: PriceConfigSchema,
Expand All @@ -78,6 +82,7 @@ export const BotConfigSchema = Type.Object({
assign: AssignSchema,
sodium: SodiumSchema,
comments: CommentsSchema,
wallet: WalletSchema,
});

export type BotConfig = Static<typeof BotConfigSchema>;
10 changes: 10 additions & 0 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,13 @@ export const getDefaultLabels = (parsedRepo: WideRepoConfig | undefined, parsedO
return [];
}
};

export const getRegisterWalletWithVerification = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): boolean => {
if (parsedRepo && parsedRepo["register-wallet-with-verification"] && typeof parsedRepo["register-wallet-with-verification"] === "boolean") {
return Boolean(parsedRepo["register-wallet-with-verification"]);
} else if (parsedOrg && parsedOrg["register-wallet-with-verification"] && typeof parsedOrg["register-wallet-with-verification"] === "boolean") {
return Boolean(parsedOrg["register-wallet-with-verification"]);
} else {
return false;
}
};
3 changes: 3 additions & 0 deletions src/utils/private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getCommentItemPrice,
getDefaultLabels,
getPromotionComment,
getRegisterWalletWithVerification,
} from "./helpers";

const CONFIG_REPO = "ubiquibot-config";
Expand Down Expand Up @@ -61,6 +62,7 @@ export interface WideConfig {
"max-concurrent-assigns"?: number;
"comment-element-pricing"?: Record<string, number>;
"default-labels"?: string[];
"register-wallet-with-verification"?: boolean;
}

export type WideRepoConfig = WideConfig;
Expand Down Expand Up @@ -143,6 +145,7 @@ export const getWideConfig = async (context: Context) => {
commentElementPricing: getCommentItemPrice(parsedRepo, parsedOrg),
defaultLabels: getDefaultLabels(parsedRepo, parsedOrg),
promotionComment: getPromotionComment(parsedRepo, parsedOrg),
registerWalletWithVerification: getRegisterWalletWithVerification(parsedRepo, parsedOrg),
};

return configData;
Expand Down

0 comments on commit 8779299

Please sign in to comment.