Skip to content

Commit

Permalink
Merge pull request #4 from gentlementlegen/generate-erc20-permit
Browse files Browse the repository at this point in the history
feat: generate erc20 / erc721 permit
  • Loading branch information
gitcoindev authored Apr 5, 2024
2 parents bcde7a0 + 2c33431 commit 984477a
Show file tree
Hide file tree
Showing 26 changed files with 387 additions and 569 deletions.
2 changes: 1 addition & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "supabase"],
"useGitignore": true,
"language": "en",
"words": ["dataurl", "devpool", "outdir", "servedir"],
"words": ["dataurl", "devpool", "outdir", "servedir", "typebox"],
"dictionaries": ["typescript", "node", "software-terms"],
"import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"],
"ignoreRegExpList": ["[0-9a-fA-F]{6}"],
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/compute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
node-version: "20.10.0"

- name: Install dependencies
run: yarn
run: yarn i --immutable --immutable-cache --check-cache

- name: Generate Permit
run: npx tsx ./src/index.ts
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/jest-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ jobs:
fetch-depth: 0
- name: Build & Run test suite
run: |
npm i -g bun
bun install
bun test | tee ./coverage.txt && exit ${PIPESTATUS[0]}
yarn install --immutable --immutable-cache --check-cache
yarn test | tee ./coverage.txt && exit ${PIPESTATUS[0]}
- name: Jest Coverage Comment
# Ensures this step is run even on previous step failure (e.g. test failed)
if: always()
Expand Down
Binary file added bun.lockb
Binary file not shown.
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@
"node": ">=20.10.0"
},
"scripts": {
"start": "tsx build/esbuild-server.ts",
"build": "tsx build/esbuild-build.ts",
"format": "run-s format:lint format:prettier format:cspell",
"format:lint": "eslint --fix .",
"format:prettier": "prettier --write .",
"format:cspell": "cspell **/*",
"knip": "knip",
"knip-ci": "knip --no-exit-code --reporter json",
"prepare": "husky install",
"worker": "wrangler dev --port 8789",
"test": "jest"
},
"keywords": [
Expand All @@ -40,7 +37,7 @@
"blake2b": "^2.1.4",
"decimal.js": "^10.4.3",
"dotenv": "^16.4.4",
"ethers": "^5.7.2",
"ethers": "6.11.1",
"libsodium-wrappers": "^0.7.13",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1"
Expand Down
4 changes: 2 additions & 2 deletions src/adapters/supabase/helpers/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export class Wallet extends Super {
throw error;
}

console.info("Successfully fetched wallet", { userId, address: data?.address });
return data?.address as `0x${string}` | undefined;
console.info("Successfully fetched wallet", { userId, address: data.address });
return data.address;
}

async getWalletByUsername(username: string) {
Expand Down
118 changes: 59 additions & 59 deletions src/handlers/generate-erc20-permit.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,48 @@
import { MaxUint256, PERMIT2_ADDRESS, PermitTransferFrom, SignatureTransfer } from "@uniswap/permit2-sdk";
import { BigNumber, ethers } from "ethers";
import { keccak256, toUtf8Bytes } from "ethers/lib/utils";
import { getPayoutConfigByNetworkId } from "../utils/payoutConfigByNetworkId";
import { decryptKeys } from "../utils/keys";
import { PermitTransactionData } from "../types/permits";
import { PERMIT2_ADDRESS, PermitTransferFrom, SignatureTransfer } from "@uniswap/permit2-sdk";
import { ethers, keccak256, MaxInt256, parseUnits, toUtf8Bytes } from "ethers";
import { Context } from "../types/context";
import { Permit } from "../types/permits";
import { decryptKeys } from "../utils/keys";
import { getPayoutConfigByNetworkId } from "../utils/payoutConfigByNetworkId";

export async function generateErc20PermitSignature(context: Context, wallet: `0x${string}`, amount: number): Promise<PermitTransactionData | string> {
export async function generateErc20PermitSignature(context: Context, username: string, amount: number): Promise<Permit> {
const config = context.config;
const logger = context.logger;
const { evmNetworkId, evmPrivateEncrypted } = config;
const { user, wallet } = context.adapters.supabase;

if (!evmPrivateEncrypted || !evmNetworkId) {
logger.fatal("EVM configuration is not defined");
throw new Error("EVM configuration is not defined");
}

const { user } = context.adapters.supabase;

const beneficiary = wallet;
const userId = user.getUserIdByWallet(beneficiary);
let issueId: number | null = null;

const userId = await user.getUserIdByUsername(username);
const walletAddress = await wallet.getWalletByUserId(userId);
let issueId: string;
if ("issue" in context.payload) {
issueId = context.payload.issue.number;
issueId = context.payload.issue.id.toString();
} else if ("pull_request" in context.payload) {
issueId = context.payload.pull_request.number;
}

if (!beneficiary) {
logger.error("No beneficiary found for permit");
return "Permit not generated: No beneficiary found for permit";
issueId = context.payload.pull_request.id.toString();
} else {
throw new Error("Issue Id is missing");
}

if (!userId) {
logger.error("No wallet found for user");
return "Permit not generated: no wallet found for user";
throw new Error("User was not found");
}
if (!walletAddress) {
const errorMessage = "ERC20 Permit generation error: Wallet not found";
logger.error(errorMessage);
throw new Error(errorMessage);
}

if (!evmPrivateEncrypted) throw logger.warn("No bot wallet private key defined");
const { rpc, paymentToken } = getPayoutConfigByNetworkId(evmNetworkId);
const { rpc, token, decimals } = getPayoutConfigByNetworkId(evmNetworkId);
const { privateKey } = await decryptKeys(evmPrivateEncrypted);

if (!rpc) throw logger.error("RPC is not defined");
if (!privateKey) throw logger.error("Private key is not defined");
if (!paymentToken) throw logger.error("Payment token is not defined");
if (!privateKey) {
const errorMessage = "Private key is not defined";
logger.fatal(errorMessage);
throw new Error(errorMessage);
}

let provider;
let adminWallet;
try {
provider = new ethers.providers.JsonRpcProvider(rpc);
provider = new ethers.JsonRpcProvider(rpc);
} catch (error) {
throw logger.debug("Failed to instantiate provider", error);
}
Expand All @@ -62,40 +55,47 @@ export async function generateErc20PermitSignature(context: Context, wallet: `0x

const permitTransferFromData: PermitTransferFrom = {
permitted: {
token: paymentToken,
amount: ethers.utils.parseUnits(amount.toString(), 18),
token: token,
amount: parseUnits(amount.toString(), decimals),
},
spender: beneficiary,
nonce: BigNumber.from(keccak256(toUtf8Bytes(`${userId}-${issueId}`))),
deadline: MaxUint256,
spender: walletAddress,
nonce: BigInt(keccak256(toUtf8Bytes(`${userId}-${issueId}`))),
deadline: MaxInt256,
};

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

const signature = await adminWallet._signTypedData(domain, types, values).catch((error) => {
throw logger.debug("Failed to sign typed data", error);
});

const transactionData = {
type: "erc20-permit",
permit: {
permitted: {
token: permitTransferFromData.permitted.token,
amount: permitTransferFromData.permitted.amount.toString(),
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(),
},
nonce: permitTransferFromData.nonce.toString(),
deadline: permitTransferFromData.deadline.toString(),
},
transferDetails: {
to: permitTransferFromData.spender,
requestedAmount: permitTransferFromData.permitted.amount.toString(),
},
types,
values
)
.catch((error) => {
const errorMessage = `Failed to sign typed data ${error}`;
logger.error(errorMessage);
throw new Error(errorMessage);
});

const erc20Permit: Permit = {
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,
} as PermitTransactionData;
};

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

return transactionData;
return erc20Permit;
}
Loading

0 comments on commit 984477a

Please sign in to comment.