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

feat: AccountApi #3

Open
wants to merge 14 commits into
base: WY_gg_extension
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 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
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# dependencies
node_modules/
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @type {import("eslint").Linter.Config} */
const config = {
root: true,
extends: ["acme"], // uses the config in `packages/config/eslint`
extends: ["gaslessgrapes"], // uses the config in `packages/config/eslint`
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
Expand Down
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[submodule "packages/smart-contracts/lib/forge-std"]
path = packages/smart-contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/forge-std"]
branch = v1.5.2
[submodule "packages/smart-contracts/lib/openzeppelin-contracts"]
path = packages/smart-contracts/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/openzeppelin-contracts"]
branch = v4.8.2
[submodule "lib/account-abstraction"]
branch = v0.4.0
9 changes: 9 additions & 0 deletions apps/browser-extension/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
collectCoverage: true,
coverageDirectory: "coverage",
preset: "ts-jest",
testEnvironment: "node",
verbose: true,
testPathIgnorePatterns: ["node_modules/"],
};
5 changes: 4 additions & 1 deletion apps/browser-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@
"devDependencies": {
"@plasmohq/prettier-plugin-sort-imports": "3.6.3",
"@types/chrome": "0.0.210",
"@types/jest": "^29.5.0",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"eslint-config-gaslessgrapes": "*",
"jest": "^29.5.0",
"prettier": "2.8.3",
"ts-jest": "^29.1.0",
"typed-emitter": "^2.1.0",
"typescript": "4.9.4"
"typescript": "^4.9.5"
},
"manifest": {
"host_permissions": [
Expand Down
38 changes: 38 additions & 0 deletions apps/browser-extension/src/app/services/AccountApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Account API
// used to invoke sign transactions?
// requires: sign function with eoa
// get scw Address?
// requires: chain
// estimate Gas: will be done by gg

import type { Transaction } from "ethers";
import { EthAddressSchema } from "~schema/EvmRequestSchema";
import type { signFunction } from "~schema/GaslessGrapesWalletOperations";

import { getTransactionHash } from "./signingUtils";

export interface AccountAPIParams {
getSignature: signFunction;
eoa: string;
chainId: number;
}

export class AccountAPI {
getSignature: signFunction;
chainId: number;
eoa: string;

constructor(params: AccountAPIParams) {
EthAddressSchema.parse(params.eoa);
this.getSignature = params.getSignature;
this.chainId = params.chainId;
this.eoa = params.eoa;
}

async createSignedTransaction(transaction: Transaction): Promise<string> {
const txnHash = getTransactionHash(transaction, this.chainId);
const signedTxn = await this.getSignature(txnHash);

return signedTxn;
}
}
64 changes: 64 additions & 0 deletions apps/browser-extension/src/app/services/signingUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ethers } from "ethers";

import type { Transaction } from "../src/schema/GaslessGrapesWalletOperations";

// Define the EIP-712 signature and type hash
const EIP_712_SIG = "EIP712Domain(string name,uint256 chainId)";
export const EIP_712_TYPE_HASH: string = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(EIP_712_SIG),
);

// Define the domain hash
export const EIP_712_DOMAIN_HASH: string = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes("GASLESS_GRAPES_WALLET"),
);

// Get the domain separator for the given chain ID
export const getDomainSeparator = (chainId: number): string => {
return ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(
["bytes32", "bytes32", "uint256"] as string[],
[EIP_712_TYPE_HASH, EIP_712_DOMAIN_HASH, chainId],
),
);
};

// Get the hash of the given transaction
export const getTxnHash = (transaction: Transaction): string => {
return ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(
["uint256", "uint256", "address", "bool", "uint256", "bytes"] as string[],
[
transaction.nonce,
transaction.gasLimit,
transaction.target,
transaction.revertOnError,
transaction.value,
transaction.data,
] as string[],
),
);
};

// Get the hash of the signed message for the given transaction and chain ID
export function getTransactionHash(
transaction: Transaction,
chainId: number,
): string {
// Get the domain separator
const domainSeparator: string = getDomainSeparator(chainId);

// Construct the message to sign
const message = ethers.utils.solidityPack(
["bytes2", "bytes32", "bytes32"] as string[],
[
ethers.utils.toUtf8Bytes("\x19\x01"),
domainSeparator,
getTxnHash(transaction),
] as string[],
);

// Get the hash of the signed message
const hashedMessage: string = ethers.utils.keccak256(message);
return hashedMessage;
}
14 changes: 14 additions & 0 deletions apps/browser-extension/src/schema/GaslessGrapesWalletOperations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { BytesLike } from "ethers";

export type signFunction = (message: BytesLike) => Promise<string>;

// TODO: Convert this to a zod schema
// Define an interface for Ethereum transactions
export interface GaslessGrapesWalletOperation {
gasLimit: bigint; // The maximum amount of gas that can be used for the transaction
value: bigint; // The amount of Ether to be transferred with the transaction
nonce: bigint; // A unique identifier for the sender's account
target: string; // The address of the contract or account that will receive the transaction
revertOnError: boolean; // Whether the transaction should revert if an error occurs
data: string; // The data to be sent with the transaction
}
32 changes: 32 additions & 0 deletions apps/browser-extension/test/AccountApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, test } from "@jest/globals";
import { type Bytes, ethers } from "ethers";
import type { signFunction } from "~schema/GaslessGrapesWalletOperations";

import {
AccountAPI,
type AccountAPIParams,
} from "../src/app/services/AccountApi";

describe("AccountApi Testing", () => {
const wallet = ethers.Wallet.createRandom();
let sigFunction: signFunction;
let params: AccountAPIParams;
beforeEach(() => {
sigFunction = (message: Bytes): Promise<string> => {
return wallet.signMessage(message);
};
params = {
getSignature: sigFunction,
eoa: wallet.address,
chainId: 1, // mainnet
};
});
test("Invalid EOA", () => {
const badEOAParams: AccountAPIParams = {
getSignature: sigFunction,
eoa: "Invalid Address",
chainId: 1, // mainnet
};
expect(() => new AccountAPI(badEOAParams)).toThrow("Invalid EOA");
});
});
49 changes: 49 additions & 0 deletions apps/browser-extension/test/signingUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, test } from "@jest/globals";
import { ethers } from "ethers";

import {
EIP_712_DOMAIN_HASH,
EIP_712_TYPE_HASH,
getDomainSeparator,
getTransactionHash,
getTxnHash,
} from "../services/signingUtils";
import type { Transaction } from "../src/schema/GaslessGrapesWalletOperations";

describe("signing tests", () => {
test("test typehash", () => {
expect(EIP_712_TYPE_HASH).toBe(
"0xcc85e4a69ca54da41cc4383bb845cbd1e15ef8a13557a6bed09b8bea2a0d92ff",
);
});
test("test domainhash", () => {
expect(EIP_712_DOMAIN_HASH).toBe(
"0x3c924ac159c18b9abc95e27241d8d48f7303b8371d5de879bcdbc55a381559eb",
);
});
test("test domainseparator", () => {
// hardcoded chain one, crosschecked with remix
expect(getDomainSeparator(1)).toBe(
"0x09f0d3d6d21027515bd4d9c8f9407fd674f4924fe5485ce3fe25cb2ef1671246",
);
});
test("basic hash", () => {
const ABI = ["function increment() view"];
const iface = new ethers.utils.Interface(ABI);
const txn: Transaction = {
gasLimit: ethers.BigNumber.from(1000000),
value: ethers.BigNumber.from(0),
nonce: ethers.BigNumber.from(12),
target: "0x5253F42a13f14a50E8783b23a787247002e7a9eC",
revertOnError: false,
data: iface.encodeFunctionData("increment"),
};
expect(getTxnHash(txn)).toBe(
"0x7cddc12e3f1c40e3d4e7a35c3966403d2cf04ff462c29ef0259598ce96c5c15a",
);

expect(getTransactionHash(txn, 1)).toBe(
"0x304ed2d2fbd66467f1d79e7b83b274dda80cb8fe92002b918b83c574888ddea3",
);
});
});
2 changes: 1 addition & 1 deletion apps/nextjs/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// @ts-ignore
c; // @ts-ignore
module.exports = require("@gg/tailwind-config/postcss");
35 changes: 35 additions & 0 deletions packages/smart-contracts/.github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Smart Contract Tests

on: workflow_dispatch

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- name: Checkout current branch
uses: actions/checkout@v3
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build
run: |
forge --version
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
17 changes: 17 additions & 0 deletions packages/smart-contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
/broadcast/**

# Dotenv file
.env
.env*.local
.secret

# dev docs
notes.txt
8 changes: 8 additions & 0 deletions packages/smart-contracts/.gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.2.0
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
branch = v4.8.0
Loading