o1-labs / o1js

TypeScript framework for zk-SNARKs and zkApps
https://docs.minaprotocol.com/en/zkapps/how-to-write-a-zkapp
Apache License 2.0
524 stars 118 forks source link

Doesn't work in Angular / Web #1242

Open AndreasGassmann opened 1 year ago

AndreasGassmann commented 1 year ago

As part of the Mina Navigators Hackathon, we built a project with multiple components. We used o1js successfully in the backend to create and deploy a contract. However, we struggled to use o1js in a frontend, created with the Angular framework.

After doing npm i o1js and trying to use it, this was the error:

import { PublicKey } from 'o1js';
Error message ```bash Error: node_modules/o1js/dist/node/bindings/crypto/bindings/curve.d.ts:4:24 - error TS2307: Cannot find module 'src/lib/ml/base.js' or its corresponding type declarations. 4 import { MlPair } from 'src/lib/ml/base.js'; ~~~~~~~~~~~~~~~~~~~~ Error: node_modules/o1js/dist/node/bindings/crypto/bindings/kimchi-types.d.ts:8:43 - error TS2307: Cannot find module '../../compiled/node_bindings/plonk_wasm.cjs' or its corresponding type declarations. 8 import type { WasmFpSrs, WasmFqSrs } from '../../compiled/node_bindings/plonk_wasm.cjs'; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/o1js/dist/node/bindings/js/node/node-backend.d.ts:4:34 - error TS2307: Cannot find module '../../compiled/node_bindings/plonk_wasm.cjs' or its corresponding type declarations. 4 export const wasm: typeof import("../../compiled/node_bindings/plonk_wasm.cjs"); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/o1js/dist/node/bindings/js/wrapper.d.ts:2:42 - error TS2307: Cannot find module '../compiled/node_bindings/plonk_wasm.cjs' or its corresponding type declarations. 2 export function getWasm(): typeof import("../compiled/node_bindings/plonk_wasm.cjs"); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/o1js/dist/node/lib/proof-system/prover-keys.d.ts:8:62 - error TS2307: Cannot find module '../../bindings/compiled/node_bindings/plonk_wasm.cjs' or its corresponding type declarations. 8 import { WasmPastaFpPlonkIndex, WasmPastaFqPlonkIndex } from '../../bindings/compiled/node_bindings/plonk_wasm.cjs'; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/o1js/dist/node/snarky.d.ts:27:8 - error TS2307: Cannot find module './bindings/compiled/node_bindings/plonk_wasm.cjs' or its corresponding type declarations. 27 } from './bindings/compiled/node_bindings/plonk_wasm.cjs'; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ```

I then tried to import the web part directly, but there were even more errors.

import { PublicKey } from 'o1js/dist/web';
Error message ```bash ./src/app/app.component.ts:1:0-42 - Error: Module not found: Error: Package path ./dist/web is not exported from package /Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js (see exports field in /Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/package.json) Error: node_modules/o1js/dist/web/bindings/lib/provable-snarky.d.ts:1:10 - error TS2305: Module '"../../snarky.js"' has no exported member 'Provable'. 1 import { Provable, ProvablePure } from '../../snarky.js'; ~~~~~~~~ Error: node_modules/o1js/dist/web/bindings/lib/provable-snarky.d.ts:1:20 - error TS2305: Module '"../../snarky.js"' has no exported member 'ProvablePure'. 1 import { Provable, ProvablePure } from '../../snarky.js'; ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/index.d.ts:1:15 - error TS2305: Module '"./snarky.js"' has no exported member 'ProvablePure'. 1 export type { ProvablePure } from './snarky.js'; ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/account_update.d.ts:316:21 - error TS2503: Cannot find namespace 'Pickles'. 316 previousProofs: Pickles.Proof[]; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/account_update.d.ts:734:38 - error TS2694: Namespace '"/Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/dist/web/snarky"' has no exported member 'ProvablePure'. 734 type: import("../snarky.js").ProvablePure<{ ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/account_update.d.ts:807:47 - error TS2694: Namespace '"/Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/dist/web/snarky"' has no exported member 'ProvablePure'. 807 callerContextType: import("../snarky.js").ProvablePure<{ ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/account_update.d.ts:896:54 - error TS2694: Namespace '"/Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/dist/web/snarky"' has no exported member 'ProvablePure'. 896 declare let ZkappPublicInput: import("../snarky.js").ProvablePure<{ ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:1:10 - error TS2305: Module '"../snarky.js"' has no exported member 'ProvablePure'. 1 import { ProvablePure, Snarky } from '../snarky.js'; ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:101:12 - error TS2503: Cannot find namespace 'Snarky'. 101 value: Snarky.Keypair; ~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:102:24 - error TS2503: Cannot find namespace 'Snarky'. 102 constructor(value: Snarky.Keypair); ~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:113:48 - error TS2694: Namespace '"/Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/dist/web/snarky"' has no exported member 'Gate'. 113 constraintSystem(): import("../snarky.js").Gate[]; ~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:119:12 - error TS2503: Cannot find namespace 'Snarky'. 119 value: Snarky.Proof; ~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:120:24 - error TS2503: Cannot find namespace 'Snarky'. 120 constructor(value: Snarky.Proof); ~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:126:12 - error TS2503: Cannot find namespace 'Snarky'. 126 value: Snarky.VerificationKey; ~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit.d.ts:127:24 - error TS2503: Cannot find namespace 'Snarky'. 127 constructor(value: Snarky.VerificationKey); ~~~~~~ Error: node_modules/o1js/dist/web/lib/circuit_value.d.ts:2:10 - error TS2305: Module '"../snarky.js"' has no exported member 'ProvablePure'. 2 import { ProvablePure } from '../snarky.js'; ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/nullifier.d.ts:30:28 - error TS2694: Namespace '"/Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/dist/web/snarky"' has no exported member 'ProvablePure'. 30 } & import("../snarky.js").ProvablePure<{ ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:1:10 - error TS2305: Module '"../snarky.js"' has no exported member 'ProvablePure'. 1 import { ProvablePure, Pickles, Gate } from '../snarky.js'; ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:1:33 - error TS2305: Module '"../snarky.js"' has no exported member 'Gate'. 1 import { ProvablePure, Pickles, Gate } from '../snarky.js'; ~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:23:12 - error TS2503: Cannot find namespace 'Pickles'. 23 proof: Pickles.Proof; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:31:16 - error TS2503: Cannot find namespace 'Pickles'. 31 proof: Pickles.Proof; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:186:14 - error TS2503: Cannot find namespace 'Pickles'. 186 provers: Pickles.Prover[]; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:187:25 - error TS2503: Cannot find namespace 'Pickles'. 187 verify: (statement: Pickles.Statement, proof: Pickles.Proof) => Promise; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:187:63 - error TS2503: Cannot find namespace 'Pickles'. 187 verify: (statement: Pickles.Statement, proof: Pickles.Proof) => Promise; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/proof_system.d.ts:199:86 - error TS2503: Cannot find namespace 'Pickles'. 199 }, { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface, gates: Gate[]): Pickles.Rule; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/provable-context.d.ts:2:10 - error TS2305: Module '"../snarky.js"' has no exported member 'Gate'. 2 import { Gate, JsonGate } from '../snarky.js'; ~~~~ Error: node_modules/o1js/dist/web/lib/provable-context.d.ts:2:16 - error TS2305: Module '"../snarky.js"' has no exported member 'JsonGate'. 2 import { Gate, JsonGate } from '../snarky.js'; ~~~~~~~~ Error: node_modules/o1js/dist/web/lib/provable.d.ts:7:10 - error TS2305: Module '"../snarky.js"' has no exported member 'Provable'. 7 import { Provable as Provable_ } from '../snarky.js'; ~~~~~~~~ Error: node_modules/o1js/dist/web/lib/zkapp.d.ts:1:10 - error TS2305: Module '"../snarky.js"' has no exported member 'Gate'. 1 import { Gate, Pickles, ProvablePure } from '../snarky.js'; ~~~~ Error: node_modules/o1js/dist/web/lib/zkapp.d.ts:1:25 - error TS2305: Module '"../snarky.js"' has no exported member 'ProvablePure'. 1 import { Gate, Pickles, ProvablePure } from '../snarky.js'; ~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/lib/zkapp.d.ts:57:23 - error TS2503: Cannot find namespace 'Pickles'. 57 static _provers?: Pickles.Prover[]; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/zkapp.d.ts:146:18 - error TS2503: Cannot find namespace 'Pickles'. 146 provers: Pickles.Prover[]; ~~~~~~~ Error: node_modules/o1js/dist/web/lib/zkapp.d.ts:147:29 - error TS2503: Cannot find namespace 'Pickles'. 147 verify: (statement: Pickles.Statement, proof: unknown) => Promise; ~~~~~~~ Error: node_modules/o1js/dist/web/snarky.d.ts:5:32 - error TS7016: Could not find a declaration file for module './bindings/js/wrapper.js'. '/Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/dist/web/bindings/js/wrapper.js' implicitly has an 'any' type. 5 import { withThreadPool } from './bindings/js/wrapper.js'; ~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/o1js/dist/web/snarky.d.ts:6:25 - error TS7016: Could not find a declaration file for module './bindings/js/wrapper.js'. '/Users/main/Programming/Personal/mina-zk-hack/frontend/node_modules/o1js/dist/web/bindings/js/wrapper.js' implicitly has an 'any' type. 6 import { getWasm } from './bindings/js/wrapper.js'; ~~~~~~~~~~~~~~~~~~~~~~~~~~ ```

So I went back to the original import { PublicKey } from 'o1js' and fixed the error. There seem to be some invalid paths, which I was able to address by creating this script:

const fs = require("fs");

const replaceInFile = (filePath, searchString, replaceString) => {
  // Read file
  fs.readFile(filePath, "utf8", (err, data) => {
    if (err) {
      console.error(err);
      return;
    }

    // Replace string
    const result = data.replace(new RegExp(searchString, "g"), replaceString);

    // Save file
    fs.writeFile(filePath, result, "utf8", (err) => {
      if (err) {
        console.error(err);
      } else {
        console.log("File successfully updated");
      }
    });
  });
};

replaceInFile(
  "node_modules/o1js/dist/node/bindings/crypto/bindings/curve.d.ts",
  "import { MlPair } from 'src/lib/ml/base.js';",
  "import { MlPair } from '../../../lib/ml/base.js';"
);
replaceInFile(
  "node_modules/o1js/dist/node/bindings/crypto/bindings/kimchi-types.d.ts",
  "import type { WasmFpSrs, WasmFqSrs } from '../../compiled/node_bindings/plonk_wasm.cjs';",
  "import type { WasmFpSrs, WasmFqSrs } from '../../compiled/_node_bindings/plonk_wasm.cjs';"
);
replaceInFile(
  "node_modules/o1js/dist/node/bindings/js/node/node-backend.d.ts",
  "../../compiled/node_bindings/plonk_wasm.cjs",
  "../../compiled/_node_bindings/plonk_wasm.cjs"
);
replaceInFile(
  "node_modules/o1js/dist/node/bindings/js/wrapper.d.ts",
  "../compiled/node_bindings/plonk_wasm.cjs",
  "../compiled/_node_bindings/plonk_wasm.cjs"
);
replaceInFile(
  "node_modules/o1js/dist/node/lib/proof-system/prover-keys.d.ts",
  "import { WasmPastaFpPlonkIndex, WasmPastaFqPlonkIndex } from '../../bindings/compiled/node_bindings/plonk_wasm.cjs';",
  "import { WasmPastaFpPlonkIndex, WasmPastaFqPlonkIndex } from '../../bindings/compiled/_node_bindings/plonk_wasm.cjs';"
);
replaceInFile(
  "node_modules/o1js/dist/node/snarky.d.ts",
  "} from './bindings/compiled/node_bindings/plonk_wasm.cjs';",
  "} from './bindings/compiled/_node_bindings/plonk_wasm.cjs';"
);

With this "fix", I was able to run the page. However, there were some new errors:

Unhandled Promise rejection: Failed to execute 'postMessage' on 'Worker': SharedArrayBuffer transfer requires self.crossOriginIsolated. ; Zone: <root> ; Task: Promise.then ; Value: DOMException: Failed to execute 'postMessage' on 'Worker': SharedArrayBuffer transfer requires self.crossOriginIsolated.
    at http://localhost:4200/vendor.js:88866:14
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (http://localhost:4200/vendor.js:103024:24)
    at _next (http://localhost:4200/vendor.js:103043:9)
    at http://localhost:4200/vendor.js:103048:7
    at new ZoneAwarePromise (http://localhost:4200/polyfills.js:9222:21)
    at http://localhost:4200/vendor.js:103040:12
    at _hr (http://localhost:4200/vendor.js:88872:14)
    at hr (http://localhost:4200/vendor.js:88860:14)
    at http://localhost:4200/vendor.js:88744:15
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (http://localhost:4200/vendor.js:103024:24)
    at _next (http://localhost:4200/vendor.js:103043:9)
    at http://localhost:4200/vendor.js:103048:7
    at new ZoneAwarePromise (http://localhost:4200/polyfills.js:9222:21)
    at http://localhost:4200/vendor.js:103040:12
    at timer (http://localhost:4200/polyfills.js:10151:27)
    at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:8236:171)
    at Zone.runTask (http://localhost:4200/polyfills.js:8040:37)
    at invokeTask (http://localhost:4200/polyfills.js:8313:26)
    at ZoneTask.invoke (http://localhost:4200/polyfills.js:8302:38)
    at data.args.<computed> (http://localhost:4200/polyfills.js:10133:26)

And also this:

Uncaught ReferenceError: _VB is not defined
    at VB (7781500a-ba12-4c67-8e65-abfacf023bba:6394:3)
    at 7781500a-ba12-4c67-8e65-abfacf023bba:6397:1

But even with those errors, o1js seems to work as it was able to create a PublicKey. I then went ahead and imported the SmartContract class I created, but then the whole thing breaks completely, again.

app.component.html:426 Unhandled Promise rejection: Cannot read properties of undefined (reading 'length') ; Zone: <root> ; Task: null ; Value: TypeError: Cannot read properties of undefined (reading 'length')
    at Ye (index.js:2080:6084)
    at bQ (index.js:2250:720)
    at H (index.js:2076:3366)
    at Reflect.G (index.js:2076:2062)
    at __decorate (tslib:57:1)
    at AcurastPriceOracle.ts:47:11 TypeError: Cannot read properties of undefined (reading 'length')
    at Ye (http://localhost:4200/vendor.js:30978:25)
    at bQ (http://localhost:4200/vendor.js:32436:11)
    at H (http://localhost:4200/vendor.js:25225:16)
    at Reflect.G (http://localhost:4200/vendor.js:25153:50)
    at __decorate (http://localhost:4200/vendor.js:127351:90)
    at http://localhost:4200/main.js:85:50

That's the contract I tried to interact with:

https://github.com/Acurast/mina-zkoracles/blob/main/contracts/src/AcurastPriceOracle.ts#L47

export class AcurastPriceOracle extends SmartContract {
  ...

  init() {
    super.init();
    ...
  }

  @method update( // <- Error happens here
    nextCounter: Field,
    priceDataBTC: Field,
    priceDataETH: Field,
    priceDataMINA: Field,
    signature: Signature
  ) {
    ...
  }
}

That's where I gave up and used the GraphQL API to fetch the contract state.

Another part of our application is that we would like to deploy a smart contract directly from the frontend. For this, it seems we could use SmartContract.compile() together with SmartContract.deploy(...).

Any help would be appreciated.

PS: There is another issue open about issues with Angular, but I don't think it's directly related, so I opened a new one: https://github.com/o1-labs/o1js/issues/910

dfstio commented 1 year ago

I would recommend to compare backend and web TypeScript settings, in particular:

 "compilerOptions": {
    "composite": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false
    }
IsaccoSordo commented 11 months ago

Hi all, the build will still fail even after adding those lines in tsconfig.json, the errors are the same as @AndreasGassmann

dfstio commented 11 months ago

You can also try to downgrade typescript libraries; I've seen the same errors after upgrading to the last versions: https://discord.com/channels/484437221055922177/1176450974873813073/1177569477521584179

directcuteo commented 9 months ago

@AndreasGassmann After a lot of time spent with the same issue in Angular I came to a bunch of conclusions:

  1. It works fine if you build a simple Javascript(+Typescript) + Webpack application and run it there
  2. It doesn't work in Angular + Webpack environment (which is 99.9% of Angular projects)
  3. It works in Angular + Vite environment (which is hard to configure) and has some tradeoffs from Angular side (harder development process).
  4. o1js relies on topLevelAwait feature. Angular by its own rules doesn't come with topLevelAwait as a default configuration and therefore it is not considered a bug if something dependent on that is not working. The flag is enabled there by Webpack, not by Angular, they take no responsibility.

o1js is doing some really strange function-string operations with the WebWorkers which most probably confuses Angular and the mechanisms underneath. I think that with Angular's latest release (v17), the o1js team should prioritize the support for Angular because it is getting more popular than ever.