Skip to content

Commit

Permalink
Merge pull request #70 from cohow/downgrade-ethers
Browse files Browse the repository at this point in the history
refactor: downgrade to ethers@v5
  • Loading branch information
0x4007 authored Sep 28, 2024
2 parents b9abc09 + 92c259f commit 0b3355d
Show file tree
Hide file tree
Showing 8 changed files with 7,001 additions and 10,227 deletions.
2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nodeLinker: node-modules
nodeLinker: node-modules
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@ubiquity-dao/rpc-handler": "1.3.0",
"@uniswap/permit2-sdk": "^1.2.0",
"dotenv": "^16.4.4",
"ethers": "6.11.1",
"ethers": "^5.7.2",
"libsodium-wrappers": "^0.7.13"
},
"devDependencies": {
Expand Down
169 changes: 86 additions & 83 deletions src/handlers/generate-erc20-permit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PERMIT2_ADDRESS, PermitTransferFrom, SignatureTransfer } from "@uniswap/permit2-sdk";
import { ethers, keccak256, MaxInt256, parseUnits, toUtf8Bytes } from "ethers";
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";
Expand All @@ -22,38 +22,38 @@ export async function generateErc20PermitSignature(
amount: number,
tokenAddress: string
): Promise<PermitReward> {
let _logger: Logger;
let logger: Logger;
const _username = username;
let _walletAddress: string | null | undefined;
let _issueNodeId: string;
let _evmNetworkId: number;
let _evmPrivateEncrypted: string;
let _userId: number;
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;
logger = contextOrPayload.logger as Logger;
walletAddress = contextOrPayload.walletAddress;
evmNetworkId = contextOrPayload.evmNetworkId;
evmPrivateEncrypted = contextOrPayload.evmPrivateEncrypted;
issueNodeId = contextOrPayload.issueNodeId;
userId = contextOrPayload.userId;
} else {
const config = contextOrPayload.config;
_logger = contextOrPayload.logger;
const { evmNetworkId, evmPrivateEncrypted } = config;
logger = contextOrPayload.logger;
const { evmNetworkId: configEvmNetworkId, evmPrivateEncrypted: configEvmPrivateEncrypted } = config;
const { data: userData } = await contextOrPayload.octokit.users.getByUsername({ username: _username });
if (!userData) {
throw new Error(`GitHub user was not found for id ${_username}`);
}
_userId = userData.id;
userId = userData.id;
const { wallet } = contextOrPayload.adapters.supabase;
_walletAddress = await wallet.getWalletByUserId(_userId);
_evmNetworkId = evmNetworkId;
_evmPrivateEncrypted = evmPrivateEncrypted;
walletAddress = await wallet.getWalletByUserId(userId);
evmNetworkId = configEvmNetworkId;
evmPrivateEncrypted = configEvmPrivateEncrypted;
if ("issue" in contextOrPayload.payload) {
_issueNodeId = contextOrPayload.payload.issue.node_id;
issueNodeId = contextOrPayload.payload.issue.node_id;
} else if ("pull_request" in contextOrPayload.payload) {
_issueNodeId = contextOrPayload.payload.pull_request.node_id;
issueNodeId = contextOrPayload.payload.pull_request.node_id;
} else {
throw new Error("Issue Id is missing");
}
Expand All @@ -62,94 +62,97 @@ export async function generateErc20PermitSignature(
if (!_username) {
throw new Error("User was not found");
}
if (!_walletAddress) {
if (!walletAddress) {
const errorMessage = "ERC20 Permit generation error: Wallet not found";
_logger.error(errorMessage);
logger.error(errorMessage);
throw new Error(errorMessage);
}

const provider = await getFastestProvider(_evmNetworkId);
const provider = await getFastestProvider(evmNetworkId);
if (!provider) {
_logger.error("Provider is not defined");
logger.error("Provider is not defined");
throw new Error("Provider is not defined");
}

let privateKey = null;
const privateKey = await getPrivateKey(evmPrivateEncrypted, logger);
const adminWallet = await getAdminWallet(privateKey, provider, logger);
const tokenDecimals = await getTokenDecimals(tokenAddress, provider, logger);

const permitTransferFromData: PermitTransferFrom = {
permitted: {
token: tokenAddress,
amount: utils.parseUnits(amount.toString(), tokenDecimals),
},
spender: walletAddress,
nonce: BigInt(utils.keccak256(utils.toUtf8Bytes(`${userId}-${issueNodeId}`))),
deadline: MaxUint256,
};

const { domain, types, values } = SignatureTransfer.getPermitData(permitTransferFromData, PERMIT2_ADDRESS, evmNetworkId);

const domainData = {
name: domain.name,
version: domain.version || "1", // default to 1 if it's undefined
chainId: domain.chainId,
verifyingContract: domain.verifyingContract,
};

try {
const privateKeyDecrypted = await decrypt(_evmPrivateEncrypted, String(process.env.X25519_PRIVATE_KEY));
const signature = await adminWallet._signTypedData(domainData, types, values);

const erc20Permit: PermitReward = {
tokenType: TokenType.ERC20,
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,
};

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

return erc20Permit;
} catch (error) {
logger.error(`Failed to sign typed data: ${error}`);
throw error;
}
}

async function getPrivateKey(evmPrivateEncrypted: string, logger: Logger) {
try {
const privateKeyDecrypted = await decrypt(evmPrivateEncrypted, String(process.env.X25519_PRIVATE_KEY));
const privateKeyParsed = parseDecryptedPrivateKey(privateKeyDecrypted);
privateKey = privateKeyParsed.privateKey;
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}`;
_logger.error(errorMessage);
logger.error(errorMessage);
throw new Error(errorMessage);
}
}

let adminWallet;
let tokenDecimals;

async function getAdminWallet(privateKey: string, provider: ethers.providers.Provider, logger: Logger) {
try {
adminWallet = new ethers.Wallet(privateKey, provider);
return new ethers.Wallet(privateKey, provider);
} catch (error) {
const errorMessage = `Failed to instantiate wallet: ${error}`;
_logger.debug(errorMessage);
logger.debug(errorMessage);
throw new Error(errorMessage);
}
}

async function getTokenDecimals(tokenAddress: string, provider: ethers.providers.Provider, logger: Logger) {
try {
const erc20Abi = ["function decimals() public view returns (uint8)"];
const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, provider);
tokenDecimals = await tokenContract.decimals();
return await tokenContract.decimals();
} catch (error) {
const errorMessage = `Failed to get token decimals for token: ${tokenAddress}`;
_logger.debug(errorMessage);
logger.debug(errorMessage);
throw new Error(errorMessage);
}

const permitTransferFromData: PermitTransferFrom = {
permitted: {
token: tokenAddress,
amount: parseUnits(amount.toString(), tokenDecimals),
},
spender: _walletAddress,
nonce: BigInt(keccak256(toUtf8Bytes(`${_userId}-${_issueNodeId}`))),
deadline: MaxInt256,
};

const { domain, types, values } = SignatureTransfer.getPermitData(permitTransferFromData, PERMIT2_ADDRESS, _evmNetworkId);

const signature = await adminWallet
.signTypedData(
{
name: domain.name,
version: domain.version,
chainId: domain.chainId ? domain.chainId.toString() : undefined,
verifyingContract: domain.verifyingContract,
salt: domain.salt?.toString(),
},
types,
values
)
.catch((error) => {
const errorMessage = `Failed to sign typed data ${error}`;
_logger.error(errorMessage);
throw new Error(errorMessage);
});

const erc20Permit: PermitReward = {
tokenType: TokenType.ERC20,
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,
};

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

return erc20Permit;
}
10 changes: 5 additions & 5 deletions src/handlers/generate-erc721-permit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MaxUint256 } from "@uniswap/permit2-sdk";
import { ethers, keccak256, toUtf8Bytes } from "ethers";
import { Wallet, utils } from "ethers";
import { Context, Logger } from "../types/context";
import { PermitReward, TokenType } from "../types";
import { isIssueEvent } from "../types/typeguards";
Expand Down Expand Up @@ -111,7 +111,7 @@ export async function generateErc721PermitSignature(
let adminWallet;

try {
adminWallet = new ethers.Wallet(_nftMinterPrivateKey, provider);
adminWallet = new Wallet(_nftMinterPrivateKey, provider);
} catch (error) {
_logger.error("Failed to instantiate wallet", error);
throw new Error("Failed to instantiate wallet");
Expand All @@ -129,8 +129,8 @@ export async function generateErc721PermitSignature(
const erc721SignatureData: Erc721PermitSignatureData = {
beneficiary: _walletAddress,
deadline: MaxUint256.toBigInt(),
keys: metadata.map(([key]) => keccak256(toUtf8Bytes(key))),
nonce: BigInt(keccak256(toUtf8Bytes(`${_userId}-${_issueNodeId}`))),
keys: metadata.map(([key]) => utils.keccak256(utils.toUtf8Bytes(key))),
nonce: BigInt(utils.keccak256(utils.toUtf8Bytes(`${_userId}-${_issueNodeId}`))),
values: metadata.map(([, value]) => value),
};

Expand All @@ -141,7 +141,7 @@ export async function generateErc721PermitSignature(
chainId: _evmNetworkId,
};

const signature = await adminWallet.signTypedData(domain, types, erc721SignatureData).catch((error: unknown) => {
const signature = await adminWallet._signTypedData(domain, types, erc721SignatureData).catch((error: unknown) => {
_logger.error("Failed to sign typed data", error);
throw new Error(`Failed to sign typed data: ${error}`);
});
Expand Down
6 changes: 3 additions & 3 deletions src/handlers/register-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers, ZeroAddress } from "ethers";
import { ethers, constants } from "ethers";
import { Context } from "../types/context";

export async function registerWallet(context: Context, address: string | null) {
Expand Down Expand Up @@ -26,7 +26,7 @@ export async function registerWallet(context: Context, address: string | null) {
return false;
}

if (address === ZeroAddress) {
if (address === constants.AddressZero) {
logger.error("Skipping to register a wallet address because user is trying to set their address to null address");
return false;
}
Expand Down Expand Up @@ -58,7 +58,7 @@ export async function registerWallet(context: Context, address: string | null) {
export async function resolveAddress(ensName: string): Promise<string | null> {
// Gets the Ethereum address associated with an ENS (Ethereum Name Service) name
// Explicitly set provider to Ethereum mainnet
const provider = new ethers.JsonRpcProvider(`https://eth.llamarpc.com`); // mainnet required for ENS
const provider = new ethers.providers.JsonRpcProvider(`https://eth.llamarpc.com`); // mainnet required for ENS
return await provider.resolveName(ensName).catch((err) => {
console.trace({ err });
return null;
Expand Down
6 changes: 3 additions & 3 deletions src/utils/get-fastest-provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RPCHandler, HandlerConstructorConfig } from "@ubiquity-dao/rpc-handler";
import { JsonRpcProvider } from "ethers";
import { providers } from "ethers";

function getHandler(networkId: number | string) {
const config = {
Expand All @@ -15,11 +15,11 @@ function getHandler(networkId: number | string) {
return new RPCHandler(config as HandlerConstructorConfig);
}

export async function getFastestProvider(networkId: number | string): Promise<JsonRpcProvider> {
export async function getFastestProvider(networkId: number | string): Promise<providers.JsonRpcProvider> {
try {
const handler = getHandler(networkId);
const provider = await handler.getFastestRpcProvider();
return new JsonRpcProvider(provider.connection.url);
return new providers.JsonRpcProvider(provider.connection.url);
} catch (e) {
throw new Error(`Failed to get fastest provider for networkId: ${networkId}`);
}
Expand Down
8 changes: 4 additions & 4 deletions tests/generate-erc721-permit.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MaxUint256 } from "@uniswap/permit2-sdk";
import { BaseWallet, keccak256, toUtf8Bytes, TypedDataDomain, TypedDataField } from "ethers";
import { Wallet, utils, TypedDataDomain, TypedDataField } from "ethers";
import { generateErc721PermitSignature } from "../src";
import { Context } from "../src/types/context";
import { Env } from "../src/types/env";
Expand Down Expand Up @@ -69,7 +69,7 @@ describe("generateErc721PermitSignature", () => {
(context.adapters.supabase.wallet.getWalletByUserId as jest.Mock).mockReturnValue(SPENDER);
(context.adapters.supabase.user.getUserIdByWallet as jest.Mock).mockReturnValue(userId);
jest
.spyOn(BaseWallet.prototype, "signTypedData")
.spyOn(Wallet.prototype, "_signTypedData")
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.mockImplementation((domain: TypedDataDomain, types: Record<string, TypedDataField[]>, value: Record<string, unknown>) => {
return Promise.resolve("0x0");
Expand Down Expand Up @@ -100,10 +100,10 @@ describe("generateErc721PermitSignature", () => {
expect(result.erc721Request?.metadata).toBeDefined();
expect(result.beneficiary).toBe(SPENDER);
expect(result.deadline).toBe(MaxUint256.toString());
expect(result.nonce).toBe(BigInt(keccak256(toUtf8Bytes(`${userId}-${issueId}`))).toString());
expect(result.nonce).toBe(BigInt(utils.keccak256(utils.toUtf8Bytes(`${userId}-${issueId}`))).toString());
expect(result.erc721Request?.values).toEqual([organizationName, repositoryName, issueNumber, userId, contributionType]);
expect(result.networkId).toBe(context.config.evmNetworkId);
const keysHashed = keys.map((key) => keccak256(toUtf8Bytes(key)));
const keysHashed = keys.map((key) => utils.keccak256(utils.toUtf8Bytes(key)));
expect(result.erc721Request?.keys).toEqual(keysHashed);
}

Expand Down
Loading

0 comments on commit 0b3355d

Please sign in to comment.