diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c37e774 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test + +on: + pull_request: + branches: + - main + push: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run prettier:check + - run: npm run build diff --git a/README.md b/README.md index b7dbdc6..4a7f818 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,7 @@ Copy `.env.example` to `.env` and update the gateway URL, ACL address, and KMS a npm run dev ``` -The server listens on [http://localhost:4173/](http://localhost:4173/) - -Note: HMR is currently broken because Vite does not handle WASM correctly. A workaround has been implemented as a script: running `npm run dev` will execute a build watch alongside Vite preview. If you have a solution for enabling the Vite dev server with HMR, feel free to open a pull request! :) +The server listens on [http://localhost:5173/](http://localhost:5173/) ## Build diff --git a/index.html b/index.html index dde16aa..1780dda 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,11 @@ - + + - Vite + Vue + TS + fhEVM Vue demo
diff --git a/package-lock.json b/package-lock.json index 59a748e..0e457e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,14 @@ "version": "0.0.0", "dependencies": { "ethers": "^6.13.4", - "fhevmjs": "^0.6.0", + "fhevmjs": "^0.6.2", "idb": "^8.0.0", + "typescript": "5.6.2", "vue": "^3.5.13" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", - "typescript": "^5.7.2", + "prettier": "^3.4.2", "vite": "^5.4.11", "vite-plugin-node-polyfills": "^0.22.0", "vue-tsc": "^2.1.10" @@ -1637,9 +1638,9 @@ } }, "node_modules/fhevmjs": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/fhevmjs/-/fhevmjs-0.6.0.tgz", - "integrity": "sha512-xukyiXv+KertTTEIhFuZp2jW31RBOXUC6TfVZBAUVEr1xiTnvXtePwuutvqj3rabnclRBTzr/j4UmEPvCaGXQw==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/fhevmjs/-/fhevmjs-0.6.2.tgz", + "integrity": "sha512-Kiq59DiqusavaCft8AzSVF2G92ZrNyf3u2dmu3zqlOxK5TGXAIY7PNICrV4xvT+pVxiAF88auNP+tH/dFqx1iA==", "license": "BSD-3-Clause-Clear", "dependencies": { "bigint-buffer": "^1.1.5", @@ -1647,9 +1648,9 @@ "fetch-mock": "^11.1.3", "keccak": "^3.0.4", "node-tfhe": "^0.9.1", - "node-tkms": "0.9.0-rc36", + "node-tkms": "^0.9.0", "tfhe": "^0.9.1", - "tkms": "0.9.0-rc36", + "tkms": "^0.9.0", "wasm-feature-detect": "^1.8.0" }, "bin": { @@ -2222,9 +2223,9 @@ "license": "BSD-3-Clause-Clear" }, "node_modules/node-tkms": { - "version": "0.9.0-rc36", - "resolved": "https://registry.npmjs.org/node-tkms/-/node-tkms-0.9.0-rc36.tgz", - "integrity": "sha512-oGqJfjvb/igd9VgQaqYbzKc+CmrnQ/eY0ShmdE3JDKXL3C4Re/tBswT0KkWmlT1aM+Lxt5ihRNOs2oHtrPqr5w==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-tkms/-/node-tkms-0.9.0.tgz", + "integrity": "sha512-ulhV23okeMW3WYnxzhvf9u87tcXqe64JqUFlvdgu64uKteG8re+zwOjzkOdxvF0xSWQbMtEU96dvcrvQM10PEg==", "license": "BSD-3-Clause-Clear" }, "node_modules/object-inspect": { @@ -2445,6 +2446,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -2774,9 +2791,9 @@ } }, "node_modules/tkms": { - "version": "0.9.0-rc36", - "resolved": "https://registry.npmjs.org/tkms/-/tkms-0.9.0-rc36.tgz", - "integrity": "sha512-8IoRi6mYgnrmwTNBe/ejvHOFRhs5M6o2Ls2RVXCgAzSVBBBWJC1O8hquqlciHfME/VooUD4iokzEaBfADvoXZw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/tkms/-/tkms-0.9.0.tgz", + "integrity": "sha512-dSzorTHvIXTYZtn6ACV/iz0GhO/kMRjqGbo3o7JJ3GNbFhsPzbIEtAQ/x+h9nJdwn//VRdsStnOAE0fxUVIGrQ==", "license": "BSD-3-Clause-Clear" }, "node_modules/tslib": { @@ -2792,10 +2809,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "devOptional": true, + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index c38eb0e..802bcb9 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,22 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "node scripts/dev.js", + "dev": "vite", "build": "vue-tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "prettier": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\"", + "prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"" }, "dependencies": { "ethers": "^6.13.4", - "fhevmjs": "^0.6.0", + "fhevmjs": "^0.6.2", "idb": "^8.0.0", + "typescript": "5.6.2", "vue": "^3.5.13" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", - "typescript": "^5.7.2", + "prettier": "^3.4.2", "vite": "^5.4.11", "vite-plugin-node-polyfills": "^0.22.0", "vue-tsc": "^2.1.10" diff --git a/scripts/dev.js b/scripts/dev.js index 01e2308..ca070c2 100644 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -1,23 +1,23 @@ -import { build, preview } from 'vite'; +import { build, preview } from "vite"; (async () => { try { await preview({ - BASE_URL: '/', - MODE: 'production', + BASE_URL: "/", + MODE: "production", DEV: false, PROD: true, }); - console.log('Preview server is running...'); - console.log('Listening on http://localhost:4173\n'); + console.log("Preview server is running..."); + console.log("Listening on http://localhost:4173\n"); const watcher = await build({ build: { watch: {}, }, }); - console.log('Build in watch mode is running...'); + console.log("Build in watch mode is running..."); } catch (err) { - console.error('Error:', err); + console.error("Error:", err); } })(); diff --git a/src/components/Connect.vue b/src/components/Connect.vue index fbb9fe8..9793304 100644 --- a/src/components/Connect.vue +++ b/src/components/Connect.vue @@ -20,8 +20,6 @@ const refreshNetwork = async () => { loading.value = true; await createFhevmInstance(); loading.value = false; - } else { - setValidNetwork(false); } }; diff --git a/src/components/Devnet.vue b/src/components/Devnet.vue index e938bc8..4928f30 100644 --- a/src/components/Devnet.vue +++ b/src/components/Devnet.vue @@ -5,13 +5,12 @@ import { getInstance } from '../fhevmjs'; const toHexString = (bytes: Uint8Array) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); -const handles = ref(null); -const inputProof = ref(null); +const handles = ref(null); +const inputProof = ref(null); const instance = getInstance(); -const encrypt = async (val: number) => { - const now = Date.now(); +const encrypt = async () => { const enc = await instance .createEncryptedInput( getAddress('0x309cf2aae85ad8a1db70ca88cfd4225bf17a7456'), diff --git a/src/fhevmStorage.ts b/src/fhevmStorage.ts index f505cea..c6fc445 100644 --- a/src/fhevmStorage.ts +++ b/src/fhevmStorage.ts @@ -1,4 +1,4 @@ -import { openDB, DBSchema, IDBPDatabase } from 'idb'; +import { openDB, DBSchema, IDBPDatabase } from "idb"; interface PublicParamsDB extends DBSchema { publicKeyStore: { @@ -17,32 +17,36 @@ interface PublicParamsDB extends DBSchema { }; } -const dbPromise: Promise> = openDB('fhevm', 1, { - upgrade(db) { - if (!db.objectStoreNames.contains('paramsStore')) { - db.createObjectStore('paramsStore', { keyPath: 'acl' }); - } - if (!db.objectStoreNames.contains('publicKeyStore')) { - db.createObjectStore('publicKeyStore', { keyPath: 'acl' }); - } +const dbPromise: Promise> = openDB( + "fhevm", + 1, + { + upgrade(db) { + if (!db.objectStoreNames.contains("paramsStore")) { + db.createObjectStore("paramsStore", { keyPath: "acl" }); + } + if (!db.objectStoreNames.contains("publicKeyStore")) { + db.createObjectStore("publicKeyStore", { keyPath: "acl" }); + } + }, }, -}); +); export async function storePublicParams( acl: string, - value: { publicParamsId: string; publicParams: Uint8Array } + value: { publicParamsId: string; publicParams: Uint8Array }, ): Promise { const db = await dbPromise; - await db.put('paramsStore', { acl, value }); + await db.put("paramsStore", { acl, value }); console.log(`Stored public params for: ${acl}`); } export async function getPublicParams( - acl: string + acl: string, ): Promise<{ publicParamsId: string; publicParams: Uint8Array } | null> { const db = await dbPromise; try { - const result = await db.get('paramsStore', acl); + const result = await db.get("paramsStore", acl); return result ? result.value : null; } catch (e) { return null; @@ -51,17 +55,19 @@ export async function getPublicParams( export async function storePublicKey( acl: string, - value: { publicKeyId: string; publicKey: Uint8Array } + value: { publicKeyId: string; publicKey: Uint8Array }, ): Promise { const db = await dbPromise; - await db.put('publicKeyStore', { acl, value }); + await db.put("publicKeyStore", { acl, value }); console.log(`Stored public key for: ${acl}`); } -export async function getPublicKey(acl: string): Promise<{ publicKeyId: string; publicKey: Uint8Array } | null> { +export async function getPublicKey( + acl: string, +): Promise<{ publicKeyId: string; publicKey: Uint8Array } | null> { const db = await dbPromise; try { - const result = await db.get('publicKeyStore', acl); + const result = await db.get("publicKeyStore", acl); return result ? result.value : null; } catch (e) { return null; diff --git a/src/fhevmjs.ts b/src/fhevmjs.ts index 227d5ee..02629f2 100644 --- a/src/fhevmjs.ts +++ b/src/fhevmjs.ts @@ -1,6 +1,11 @@ -import { isAddress } from 'ethers'; -import { initFhevm, createInstance, FhevmInstance } from 'fhevmjs'; -import { getPublicKey, getPublicParams, storePublicKey, storePublicParams } from './fhevmStorage'; +import { isAddress } from "ethers"; +import { initFhevm, createInstance, FhevmInstance } from "fhevmjs/bundle"; +import { + getPublicKey, + getPublicParams, + storePublicKey, + storePublicParams, +} from "./fhevmStorage"; const ACL_ADDRESS: string = import.meta.env.VITE_ACL_ADDRESS; @@ -16,9 +21,7 @@ type Keypairs = { }; }; -export const init = async () => { - await initFhevm({ thread: navigator.hardwareConcurrency }); -}; +export const init = initFhevm; let instancePromise: Promise; let instance: FhevmInstance; @@ -33,7 +36,7 @@ export const createFhevmInstance = async () => { const storedPublicParams = await getPublicParams(ACL_ADDRESS); const publicParams = storedPublicParams ? { - '2048': storedPublicParams, + "2048": storedPublicParams, } : null; instancePromise = createInstance({ @@ -56,14 +59,23 @@ export const createFhevmInstance = async () => { } }; -export const setKeypair = (contractAddress: string, userAddress: string, keypair: Keypair) => { +export const setKeypair = ( + contractAddress: string, + userAddress: string, + keypair: Keypair, +) => { if (!isAddress(contractAddress) || !isAddress(userAddress)) return; keypairs[userAddress][contractAddress] = keypair; }; -export const getKeypair = (contractAddress: string, userAddress: string): Keypair | null => { +export const getKeypair = ( + contractAddress: string, + userAddress: string, +): Keypair | null => { if (!isAddress(contractAddress) || !isAddress(userAddress)) return null; - return keypairs[userAddress] ? keypairs[userAddress][contractAddress] || null : null; + return keypairs[userAddress] + ? keypairs[userAddress][contractAddress] || null + : null; }; export const getInstance = (): FhevmInstance => { diff --git a/src/main.ts b/src/main.ts index 2425c0f..3c9bfeb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,5 @@ -import { createApp } from 'vue' -import './style.css' -import App from './App.vue' +import { createApp } from "vue"; +import "./style.css"; +import App from "./App.vue"; -createApp(App).mount('#app') +createApp(App).mount("#app"); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 31abf91..e2857a2 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,5 +1,7 @@ /// interface Window { - ethereum: import('ethers').Eip1193Provider & { on: (event: string, cb: (param: any) => any) => void }; + ethereum: import("ethers").Eip1193Provider & { + on: (event: string, cb: (param: any) => any) => void; + }; } diff --git a/vite.config.ts b/vite.config.ts index 185a193..0f41013 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,23 +1,14 @@ -import { defineConfig } from 'vite'; -import vue from '@vitejs/plugin-vue'; -import { nodePolyfills } from 'vite-plugin-node-polyfills'; +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; export default defineConfig({ - assetsInclude: ['**/*.bin'], + assetsInclude: ["**/*.bin"], plugins: [vue(), nodePolyfills()], server: { - port: 9000, headers: { - 'Cross-Origin-Opener-Policy': 'same-origin', - 'Cross-Origin-Embedder-Policy': 'require-corp', - }, - }, - worker: { - format: 'es', - rollupOptions: { - output: { - entryFileNames: '[name].js', - }, + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", }, }, });