diff --git a/.cspell.json b/.cspell.json index d0b404cf..fb29ee7c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -1,3 +1,41 @@ { - "words": ["WXDAI"] + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "/lib"], + "useGitignore": true, + "language": "en", + "words": [ + "solmate", + "binkey", + "binsec", + "cirip", + "dataurl", + "devpool", + "ethersproject", + "fract", + "gnosisscan", + "godb", + "greyscale", + "IERC", + "keccak", + "keypair", + "libsodium", + "Numberish", + "outdir", + "Rpcs", + "scalarmult", + "servedir", + "sonarjs", + "typebox", + "TYPEHASH", + "ubiquibot", + "UBIQUIBOT", + "URLSAFE", + "WXDAI", + "XDAI", + "xmark" + ], + "dictionaries": ["typescript", "node", "software-terms", "html"], + "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], + "ignoreRegExpList": ["[0-9a-fA-F]{6}"] } diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..22340c8a --- /dev/null +++ b/.eslintrc @@ -0,0 +1,108 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": ["./tsconfig.json"] + }, + "plugins": ["@typescript-eslint", "sonarjs"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:sonarjs/recommended"], + "ignorePatterns": ["**/*.js"], + "rules": { + "prefer-arrow-callback": [ + "warn", + { + "allowNamedFunctions": true + } + ], + "func-style": [ + "warn", + "declaration", + { + "allowArrowFunctions": false + } + ], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-non-null-assertion": "error", + "constructor-super": "error", + "no-invalid-this": "off", + "@typescript-eslint/no-invalid-this": ["error"], + "no-restricted-syntax": ["error", "ForInStatement"], + "use-isnan": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "args": "after-used", + "ignoreRestSiblings": true, + "vars": "all", + "varsIgnorePattern": "^_", + "argsIgnorePattern": "^_" + } + ], + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/restrict-plus-operands": "error", + "sonarjs/no-all-duplicated-branches": "error", + "sonarjs/no-collection-size-mischeck": "error", + "sonarjs/no-duplicated-branches": "error", + "sonarjs/no-element-overwrite": "error", + "sonarjs/no-identical-conditions": "error", + "sonarjs/no-identical-expressions": "error", + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": ["PascalCase"], + "custom": { + "regex": "^I[A-Z]", + "match": false + } + }, + { + "selector": "memberLike", + "modifiers": ["private"], + "format": ["camelCase"], + "leadingUnderscore": "require" + }, + { + "selector": "typeLike", + "format": ["PascalCase"] + }, + { + "selector": "typeParameter", + "format": ["PascalCase"], + "prefix": ["T"] + }, + { + "selector": "variable", + "format": ["camelCase", "UPPER_CASE"], + "leadingUnderscore": "allow", + "trailingUnderscore": "allow" + }, + { + "selector": "variable", + "format": ["camelCase"], + "leadingUnderscore": "allow", + "trailingUnderscore": "allow" + }, + { + "selector": "variable", + "modifiers": ["destructured"], + "format": null + }, + { + "selector": "variable", + "types": ["boolean"], + "format": ["PascalCase"], + "prefix": ["is", "should", "has", "can", "did", "will", "does"] + }, + { + "selector": "variableLike", + "format": ["camelCase"] + }, + { + "selector": ["function", "variable"], + "format": ["camelCase"] + } + ] + } +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..cde98436 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,6 @@ +Resolves # + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f889017f..503dac78 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,49 +1,54 @@ -name: Build +name: Build & Deploy on: push: - branches: - - development - pull_request: workflow_dispatch: jobs: build: - name: Build runs-on: ubuntu-22.04 permissions: contents: read - steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Check out repository + uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v3 + - name: Set up Node.js + uses: actions/setup-node@v4 with: - node-version: "20.10.0" - - - name: Yarn Install - run: yarn install + node-version: 20.10.0 - name: Build - run: yarn utils:build - - - name: Add commit SHA file to build folder - run: echo -n $(echo "${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}" | cut -c1-7) > static/commit.txt - - - name: Prepare Deployment Artifact - env: - EVENT_NAME: ${{github.event_name}} - PR_NUMBER: ${{ github.event.number }} - SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} run: | - echo "event_name=$EVENT_NAME,pr_number=$PR_NUMBER,sha=$SHA" - mkdir -p ./pr - echo "event_name=$EVENT_NAME,pr_number=$PR_NUMBER,sha=$SHA," > ./pr/pr_number - cd ./static && zip -r ../pr/pull-request.zip ./* + yarn + yarn build + echo -n $(echo "${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}" | cut -c1-7) > static/commit.txt - - uses: actions/upload-artifact@v3 + - name: Check Cloudflare API Token + id: check_token + run: | + if [[ -z "${{ secrets.CLOUDFLARE_API_TOKEN }}" ]]; then + echo "Cloudflare API token is not set. Skipping deployment." + echo "skip=true" >> $GITHUB_ENV + else + echo "skip=false" >> $GITHUB_ENV + fi + shell: bash + + - name: Deploy to Cloudflare + if: env.skip != 'true' + uses: ubiquity/cloudflare-deploy-action@main with: - name: pr - path: pr/ + cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + repository: ${{ github.repository }} + production_branch: ${{ github.event.repository.default_branch }} + output_directory: "static" + current_branch: ${{ github.ref_name }} + pull_request_number: ${{ github.event.pull_request.number }} + commit_sha: ${{ github.sha }} + app_private_key: ${{ secrets.APP_PRIVATE_KEY }} + app_id: ${{ secrets.APP_ID }} + app_installation_id: ${{ secrets.APP_INSTALLATION_ID }} + # Add any environment variables you need to pass along here + # SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + # SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} diff --git a/.github/workflows/continuous-deploy.yml b/.github/workflows/continuous-deploy.yml deleted file mode 100644 index 6f11eeff..00000000 --- a/.github/workflows/continuous-deploy.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Deploy to Cloudflare Pages - -on: - workflow_run: - workflows: ["Build"] - types: - - completed - -jobs: - get-default-branch: - runs-on: ubuntu-latest - outputs: - isDefault: ${{ steps.check.outputs.isDefault }} - steps: - - name: Check if current branch is default - id: check - run: | - DEFAULT_BRANCH=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }} | jq -r '.default_branch') - IS_DEFAULT=false - if [ "$DEFAULT_BRANCH" = "${{ github.ref }}" ]; then - IS_DEFAULT=true - fi - echo "::set-output name=isDefault::$IS_DEFAULT" - - deploy-to-cloudflare: - name: Deploy to Cloudflare Pages - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ubiquity/.github/.github/workflows/deploy.yml@main - with: - deploy_preview_branches: ${{ needs.get-default-branch.outputs.isDefault }} - permissions: - contents: read - pull-requests: write - secrets: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - APP_ID: ${{ secrets.APP_ID }} - APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml new file mode 100644 index 00000000..6b3066e5 --- /dev/null +++ b/.github/workflows/conventional-commits.yml @@ -0,0 +1,12 @@ +name: Conventional Commits + +on: + push: + +jobs: + conventional-commits: + name: Conventional Commits + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ubiquity/action-conventional-commits@master diff --git a/.github/workflows/cspell.yml b/.github/workflows/cspell.yml new file mode 100644 index 00000000..c81c97a0 --- /dev/null +++ b/.github/workflows/cspell.yml @@ -0,0 +1,24 @@ +name: Spell Check + +on: + push: + +jobs: + spellcheck: + name: Check for spelling errors + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.10.0" + + - name: Install cspell + run: yarn add cspell + + - name: Run cspell + run: yarn format:cspell diff --git a/.gitignore b/.gitignore index 22c821ee..d01f79ae 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,9 @@ out .yarn .DS_Store commit.txt +# macOS +# node +# yarn2 +.pnp.cjs +.pnp.loader.mjs +static/dist \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index a3706a83..3d81c6bd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,7 @@ [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 +[submodule "lib/ubiquibot"] + path = lib/ubiquibot + url = https://github.com/ubiquity/ubiquibot + shallow = true diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 00000000..b78bacbd --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn commitlint --edit "$1" \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..5a182ef1 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..69a671ca --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules +/lib \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 9bdce37f..816d7f8c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,12 +1,10 @@ { - "useTabs": false, + "trailingComma": "es5", + "tabWidth": 2, "semi": true, - "trailingComma": "all", - "bracketSpacing": true, - "arrowParens": "avoid", + "singleQuote": false, "printWidth": 160, - "proseWrap": "preserve", - "tabWidth": 2, "htmlWhitespaceSensitivity": "strict", - "singleQuote": false + "plugins": [], + "useTabs": false } diff --git a/README.md b/README.md index e8083985..8038e097 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ http://localhost:8080?claim=eyJwZXJtaXQiOnsicGVybWl0dGVkIjp7InRva2VuIjoiMHgxMWZF This section describes how to invalidate the following [permit](https://github.com/ubiquity/ubiquity-dollar/issues/643#issuecomment-1607152588) (i.e. invalidate a permit2 nonce) -1. Setup `.env` file with the required env varibales: `NONCE` (nonce number), `NONCE_SIGNER_ADDRESS` (i.e. the bot's wallet) and `RPC_PROVIDER_URL`. For this [permit URL](https://github.com/ubiquity/ubiquity-dollar/issues/643#issuecomment-1607152588) the `.env` file will look like this: +1. Setup `.env` file with the required env variables: `NONCE` (nonce number), `NONCE_SIGNER_ADDRESS` (i.e. the bot's wallet) and `RPC_PROVIDER_URL`. For this [permit URL](https://github.com/ubiquity/ubiquity-dollar/issues/643#issuecomment-1607152588) the `.env` file will look like this: ``` NONCE="9867970486646789738815952475601005014850694197864057371518032581271992954680" diff --git a/build/esbuild-build.ts b/build/esbuild-build.ts index bd358a93..7453b667 100644 --- a/build/esbuild-build.ts +++ b/build/esbuild-build.ts @@ -1,3 +1,35 @@ import esbuild from "esbuild"; -import { esBuildContext } from "./esbuild-config"; -esbuild.build(esBuildContext); +const typescriptEntries = [ + "static/scripts/rewards/index.ts", + "static/scripts/audit-report/audit.ts", + "static/scripts/onboarding/onboarding.ts", + "static/scripts/key-generator/keygen.ts", +]; +const cssEntries = ["static/styles/rewards/rewards.css", "static/styles/audit-report/audit.css", "static/styles/onboarding/onboarding.css"]; +export const entries = [...typescriptEntries, ...cssEntries]; + +export const esBuildContext: esbuild.BuildOptions = { + sourcemap: true, + entryPoints: entries, + bundle: true, + minify: false, + loader: { + ".png": "dataurl", + ".woff": "dataurl", + ".woff2": "dataurl", + ".eot": "dataurl", + ".ttf": "dataurl", + ".svg": "dataurl", + }, + outdir: "static/out", +}; + +esbuild + .build(esBuildContext) + .then(() => { + console.log("\tesbuild complete"); + }) + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/build/esbuild-config.ts b/build/esbuild-config.ts deleted file mode 100644 index 7c7a681f..00000000 --- a/build/esbuild-config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import esbuild from "esbuild"; -import { invertColors } from "./plugins/invert-colors"; -const typescriptEntries = [ - "static/scripts/rewards/index.ts", - "static/scripts/audit-report/audit.ts", - "static/scripts/onboarding/onboarding.ts", - "static/scripts/key-generator/keygen.ts", -]; -const CSSEntries = ["static/styles/rewards/rewards.css", "static/styles/audit-report/audit.css", "static/styles/onboarding/onboarding.css"]; -export const entries = [...typescriptEntries, ...CSSEntries]; - -export let esBuildContext = { - sourcemap: true, - entryPoints: entries, - //plugins: [invertColors], - bundle: true, - minify: false, - loader: { - ".png": "dataurl", - ".woff": "dataurl", - ".woff2": "dataurl", - ".eot": "dataurl", - ".ttf": "dataurl", - ".svg": "dataurl", - }, - outdir: "static/out", -} as esbuild.BuildOptions; diff --git a/build/esbuild-server.ts b/build/esbuild-server.ts index fd7654e9..bb992aee 100644 --- a/build/esbuild-server.ts +++ b/build/esbuild-server.ts @@ -1,11 +1,18 @@ import esbuild from "esbuild"; -import { esBuildContext } from "./esbuild-config"; +import { esBuildContext } from "./esbuild-build"; -async function server() { - const ctx = await esbuild.context(esBuildContext); - const { host, port } = await ctx.serve({ +(async () => { + await server(); +})().catch((error) => { + console.error("Unhandled error:", error); + process.exit(1); +}); + +export async function server() { + const _context = await esbuild.context(esBuildContext); + const { port } = await _context.serve({ servedir: "static", port: 8080, }); + console.log(`http://localhost:${port}`); } -server(); diff --git a/build/index.ts b/build/index.ts new file mode 100644 index 00000000..e1cf0b87 --- /dev/null +++ b/build/index.ts @@ -0,0 +1,4 @@ +import * as dotenv from "dotenv"; +// load environment variables (if you have them) +dotenv.config(); +console.log("Welcome to ts-template"); diff --git a/build/plugins/invert-colors.ts b/build/plugins/invert-colors.ts index 9270c27e..88af8b76 100644 --- a/build/plugins/invert-colors.ts +++ b/build/plugins/invert-colors.ts @@ -5,10 +5,10 @@ import path, { basename, dirname } from "path"; export const invertColors: esbuild.Plugin = { name: "invert-colors", setup(build) { - build.onEnd(async result => { + build.onEnd(async (result) => { console.log(result); }); - build.onLoad({ filter: /\.css$/ }, async args => { + build.onLoad({ filter: /\.css$/ }, async (args) => { const contents = await fs.promises.readFile(args.path, "utf8"); const updatedContents = contents.replace(/prefers-color-scheme: dark/g, "prefers-color-scheme: light"); diff --git a/globals.d.ts b/globals.d.ts index 2f29226d..4ab599ac 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -4,10 +4,10 @@ export interface EthereumIsh { isMetaMask?: boolean; isStatus?: boolean; networkVersion: string; - selectedAddress: any; + selectedAddress: string; - on(event: "close" | "accountsChanged" | "chainChanged" | "networkChanged", callback: (payload: any) => void): void; - once(event: "close" | "accountsChanged" | "chainChanged" | "networkChanged", callback: (payload: any) => void): void; + on(event: "close" | "accountsChanged" | "chainChanged" | "networkChanged", callback: (payload: unknown) => void): void; + once(event: "close" | "accountsChanged" | "chainChanged" | "networkChanged", callback: (payload: unknown) => void): void; } declare global { diff --git a/lib/ubiquibot b/lib/ubiquibot new file mode 160000 index 00000000..cb084909 --- /dev/null +++ b/lib/ubiquibot @@ -0,0 +1 @@ +Subproject commit cb084909ab332f67bba271b5a17b907d21716bd1 diff --git a/package.json b/package.json index 1be823f3..cd83b821 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,89 @@ { + "name": "pay.ubq.fi", + "version": "1.0.0", + "description": "Claim your DevPool rewards.", + "main": "build/index.ts", + "author": "Ubiquity DAO", "license": "MIT", + "engines": { + "node": ">=20.10.0" + }, "scripts": { "start": "run-s utils:hash start:sign start:ui", - "format": "prettier --write .", + "format": "run-s format:lint format:prettier format:cspell", "build": "run-s utils:hash utils:build", "start:ui": "tsx build/esbuild-server.ts", "start:sign": "tsx scripts/typescript/generate-permit2-url.ts", "utils:build": "tsx build/esbuild-build.ts", "utils:hash": "git rev-parse --short HEAD > static/commit.txt", - "utils:get-invalidate-params": "forge script --via-ir scripts/solidity/GetInvalidateNonceParams.s.sol" + "utils:get-invalidate-params": "forge script --via-ir scripts/solidity/GetInvalidateNonceParams.s.sol", + "format:lint": "eslint --fix .", + "format:prettier": "prettier --write .", + "format:cspell": "cspell **/*", + "prepare": "husky install" }, + "keywords": [ + "typescript", + "template", + "dao", + "ubiquity", + "open-source" + ], "dependencies": { "@ethersproject/providers": "^5.7.2", - "@octokit/core": "^4.2.0", - "@octokit/plugin-create-or-update-text-file": "^2.0.4", - "@octokit/plugin-throttling": "^5.1.0", - "@octokit/rest": "^19.0.7", - "@sinclair/typebox": "^0.31.28", - "@types/libsodium-wrappers": "^0.7.10", - "@types/node": "^18.15.11", + "@octokit/core": "^5.1.0", + "@octokit/plugin-create-or-update-text-file": "^4.0.1", + "@octokit/plugin-throttling": "^8.1.3", + "@octokit/rest": "^20.0.2", + "@sinclair/typebox": "^0.32.14", + "@types/libsodium-wrappers": "^0.7.13", "@uniswap/permit2-sdk": "^1.2.0", - "axios": "^1.3.5", - "dotenv": "^16.0.3", - "esbuild": "^0.17.15", + "axios": "^1.6.7", + "dotenv": "^16.4.4", "ethers": "^5.7.2", "godb": "^0.6.2", - "libsodium-wrappers": "^0.7.11", + "libsodium-wrappers": "^0.7.13", "npm-run-all": "^4.1.5", - "prettier": "^2.8.7", - "retry-axios": "^3.0.0", - "tsx": "^3.12.6", - "typescript": "^5.1.6", - "yaml": "^2.2.2" + "retry-axios": "^3.1.3", + "yaml": "^2.3.4" }, - "engines": { - "node": ">=20.10.0" + "devDependencies": { + "@commitlint/cli": "^18.6.1", + "@commitlint/config-conventional": "^18.6.2", + "@cspell/dict-node": "^4.0.3", + "@cspell/dict-software-terms": "^3.3.18", + "@cspell/dict-typescript": "^3.1.2", + "@types/node": "^20.11.19", + "@typescript-eslint/eslint-plugin": "^7.0.1", + "@typescript-eslint/parser": "^7.0.1", + "cspell": "^8.3.2", + "cspell-dict-html": "^1.2.5", + "esbuild": "^0.20.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-sonarjs": "^0.24.0", + "husky": "^9.0.11", + "knip": "^5.0.1", + "lint-staged": "^15.2.2", + "npm-run-all": "^4.1.5", + "prettier": "^3.2.5", + "tsx": "^4.7.1", + "typescript": "^5.3.3", + "yarn-upgrade-all": "^0.7.2" + }, + "lint-staged": { + "*.ts": [ + "yarn prettier --write", + "eslint --fix" + ], + "src/**.{ts,json}": [ + "cspell" + ] + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] } } diff --git a/scripts/typescript/generate-permit2-url.ts b/scripts/typescript/generate-permit2-url.ts index 81f7a3ff..7abf435d 100644 --- a/scripts/typescript/generate-permit2-url.ts +++ b/scripts/typescript/generate-permit2-url.ts @@ -2,12 +2,12 @@ import { MaxUint256, PermitTransferFrom, SignatureTransfer } from "@uniswap/perm import { randomBytes } from "crypto"; import * as dotenv from "dotenv"; import { BigNumber, ethers } from "ethers"; -import { verifyEnvironmentVariables, log, colorizeText } from "./utils"; +import { log, verifyEnvironmentVariables } from "./utils"; dotenv.config(); const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on all chains -generate().catch(error => { +generate().catch((error) => { console.error(error); verifyEnvironmentVariables(); process.exitCode = 1; @@ -34,7 +34,7 @@ async function generate() { const { domain, types, values } = SignatureTransfer.getPermitData( permitTransferFromData, PERMIT2_ADDRESS, - process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : 1, + process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : 1 ); const signature = await myWallet._signTypedData(domain, types, values); const txData = [ diff --git a/static/audit.html b/static/audit.html index 192f814e..bf505dc1 100644 --- a/static/audit.html +++ b/static/audit.html @@ -1,4 +1,4 @@ - +