Skip to content

Commit

Permalink
Merge pull request #1045 from o1-labs/feature/improve-web-worker-inte…
Browse files Browse the repository at this point in the history
…rface

Improve web worker interface
  • Loading branch information
ymekuria authored Oct 9, 2024
2 parents a8baa72 + 1ca1e7d commit f67c1cc
Show file tree
Hide file tree
Showing 18 changed files with 397 additions and 481 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions examples/zkapps/04-zkapp-browser-ui/ui/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "../styles/globals.css";

export const metadata = {
title: 'Mina zkApp UI',
description: 'built with o1js',
icons: {
icon: '/assets/favicon.ico',
},
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
252 changes: 252 additions & 0 deletions examples/zkapps/04-zkapp-browser-ui/ui/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
'use client';
import { Field } from 'o1js';
import { useEffect, useState } from 'react';
import GradientBG from '../components/GradientBG';
import styles from '../styles/Home.module.css';
import './reactCOIServiceWorker';
import ZkappWorkerClient from './zkappWorkerClient';

let transactionFee = 0.1;
const ZKAPP_ADDRESS = 'B62qpXPvmKDf4SaFJynPsT6DyvuxMS9H1pT4TGonDT26m599m7dS9gP';

export default function Home() {
const [zkappWorkerClient, setZkappWorkerClient] = useState<null | ZkappWorkerClient>(null);
const [hasWallet, setHasWallet] = useState<null | boolean>(null);
const [hasBeenSetup, setHasBeenSetup] = useState(false);
const [accountExists, setAccountExists] = useState(false);
const [currentNum, setCurrentNum] = useState<null | Field>(null);
const [publicKeyBase58, setPublicKeyBase58] = useState('');
const [creatingTransaction, setCreatingTransaction] = useState(false);
const [displayText, setDisplayText] = useState('');
const [transactionlink, setTransactionLink] = useState('');

const displayStep = (step: string) => {
setDisplayText(step)
console.log(step)
}

// -------------------------------------------------------
// Do Setup

useEffect(() => {
const setup = async () => {
try {
if (!hasBeenSetup) {
displayStep('Loading web worker...')
const zkappWorkerClient = new ZkappWorkerClient();
setZkappWorkerClient(zkappWorkerClient);
await new Promise((resolve) => setTimeout(resolve, 5000));
displayStep('Done loading web worker')

await zkappWorkerClient.setActiveInstanceToDevnet();

const mina = (window as any).mina;
if (mina == null) {
setHasWallet(false);
displayStep('Wallet not found.');
return;
}

const publicKeyBase58: string = (await mina.requestAccounts())[0];
setPublicKeyBase58(publicKeyBase58);
displayStep(`Using key:${publicKeyBase58}`);

displayStep('Checking if fee payer account exists...');
const res = await zkappWorkerClient.fetchAccount(
publicKeyBase58,
);
const accountExists = res.error === null;
setAccountExists(accountExists);

await zkappWorkerClient.loadContract();

displayStep('Compiling zkApp...');
await zkappWorkerClient.compileContract();
displayStep('zkApp compiled');

await zkappWorkerClient.initZkappInstance(ZKAPP_ADDRESS);

displayStep('Getting zkApp state...');
await zkappWorkerClient.fetchAccount(ZKAPP_ADDRESS);
const currentNum = await zkappWorkerClient.getNum();
setCurrentNum(currentNum);
console.log(`Current state in zkApp: ${currentNum}`);


setHasBeenSetup(true);
setHasWallet(true);
setDisplayText('');

}
} catch (error: any) {
displayStep(`Error during setup: ${error.message}`);
}
};

setup();
}, []);

// -------------------------------------------------------
// Wait for account to exist, if it didn't

useEffect(() => {
const checkAccountExists = async () => {
if (hasBeenSetup && !accountExists) {
try {
for (;;) {
displayStep('Checking if fee payer account exists...');

const res = await zkappWorkerClient!.fetchAccount(publicKeyBase58);
const accountExists = res.error == null;
if (accountExists) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}
} catch (error: any) {
displayStep(`Error checking account: ${error.message}`);
}

}
setAccountExists(true);
};

checkAccountExists();
}, [zkappWorkerClient, hasBeenSetup, accountExists]);

// -------------------------------------------------------
// Send a transaction

const onSendTransaction = async () => {
setCreatingTransaction(true);
displayStep('Creating a transaction...');

console.log('publicKeyBase58 sending to worker', publicKeyBase58);
await zkappWorkerClient!.fetchAccount(publicKeyBase58);

await zkappWorkerClient!.createUpdateTransaction();

displayStep('Creating proof...');
await zkappWorkerClient!.proveUpdateTransaction();

displayStep('Requesting send transaction...');
const transactionJSON = await zkappWorkerClient!.getTransactionJSON();

displayStep('Getting transaction JSON...');
const { hash } = await (window as any).mina.sendTransaction({
transaction: transactionJSON,
feePayer: {
fee: transactionFee,
memo: '',
},
});

const transactionLink = `https://minascan.io/devnet/tx/${hash}`;
setTransactionLink(transactionLink);
setDisplayText(transactionLink);

setCreatingTransaction(true);
};

// -------------------------------------------------------
// Refresh the current state

const onRefreshCurrentNum = async () => {
try {
displayStep('Getting zkApp state...');
await zkappWorkerClient!.fetchAccount(ZKAPP_ADDRESS);
const currentNum = await zkappWorkerClient!.getNum();
setCurrentNum(currentNum);
console.log(`Current state in zkApp: ${currentNum}`);
setDisplayText('');
} catch (error: any) {
displayStep(`Error refreshing state: ${error.message}`);
}
};

// -------------------------------------------------------
// Create UI elements

let auroLinkElem;
if (hasWallet === false) {
const auroLink = 'https://www.aurowallet.com/';
auroLinkElem = (
<div>
Could not find a wallet.{' '}
<a href="https://www.aurowallet.com/" target="_blank" rel="noreferrer">
Install Auro wallet here
</a>
</div>
);
}

const stepDisplay = transactionlink ? (
<a
href={transactionlink}
target="_blank"
rel="noreferrer"
style={{ textDecoration: 'underline' }}
>
View transaction
</a>
) : (
displayText
);

let setup = (
<div
className={styles.start}
style={{ fontWeight: 'bold', fontSize: '1.5rem', paddingBottom: '5rem' }}
>
{stepDisplay}
{auroLinkElem}
</div>
);

let accountDoesNotExist;
if (hasBeenSetup && !accountExists) {
const faucetLink =
`https://faucet.minaprotocol.com/?address='${publicKeyBase58}`;
accountDoesNotExist = (
<div>
<span style={{ paddingRight: '1rem' }}>Account does not exist.</span>
<a href={faucetLink} target="_blank" rel="noreferrer">
Visit the faucet to fund this fee payer account
</a>
</div>
);
}

let mainContent;
if (hasBeenSetup && accountExists) {
mainContent = (
<div style={{ justifyContent: 'center', alignItems: 'center' }}>
<div className={styles.center} style={{ padding: 0 }}>
Current state in zkApp: {currentNum?.toString()}{' '}
</div>
<button
className={styles.card}
onClick={onSendTransaction}
disabled={creatingTransaction}
>
Send Transaction
</button>
<button className={styles.card} onClick={onRefreshCurrentNum}>
Get Latest State
</button>
</div>
);
}

return (
<GradientBG>
<div className={styles.main} style={{ padding: 0 }}>
<div className={styles.center} style={{ padding: 0 }}>
{setup}
{accountDoesNotExist}
{mainContent}
</div>
</div>
</GradientBG>
);
}
52 changes: 52 additions & 0 deletions examples/zkapps/04-zkapp-browser-ui/ui/app/zkappWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Mina, PublicKey, fetchAccount } from 'o1js';
import * as Comlink from "comlink";
import type { Add } from '../../contracts/src/Add';

type Transaction = Awaited<ReturnType<typeof Mina.transaction>>;

const state = {
AddInstance: null as null | typeof Add,
zkappInstance: null as null | Add,
transaction: null as null | Transaction,
};

export const api = {
async setActiveInstanceToDevnet() {
const Network = Mina.Network('https://api.minascan.io/node/devnet/v1/graphql');
console.log('Devnet network instance configured');
Mina.setActiveInstance(Network);
},
async loadContract() {
const { Add } = await import('../../contracts/build/src/Add.js');
state.AddInstance = Add;
},
async compileContract() {
await state.AddInstance!.compile();
},
async fetchAccount(publicKey58: string) {
const publicKey = PublicKey.fromBase58(publicKey58);
return fetchAccount({ publicKey });
},
async initZkappInstance(publicKey58: string) {
const publicKey = PublicKey.fromBase58(publicKey58);
state.zkappInstance = new state.AddInstance!(publicKey);
},
async getNum() {
const currentNum = await state.zkappInstance!.num.get();
return JSON.stringify(currentNum.toJSON());
},
async createUpdateTransaction() {
state.transaction = await Mina.transaction(async () => {
await state.zkappInstance!.update();
});
},
async proveUpdateTransaction() {
await state.transaction!.prove();
},
async getTransactionJSON() {
return state.transaction!.toJSON();
},
};

// Expose the API to be used by the main thread
Comlink.expose(api);
54 changes: 54 additions & 0 deletions examples/zkapps/04-zkapp-browser-ui/ui/app/zkappWorkerClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Field } from 'o1js';
import * as Comlink from "comlink";

export default class ZkappWorkerClient {
// ---------------------------------------------------------------------------------------
worker: Worker;
// Proxy to interact with the worker's methods as if they were local
remoteApi: Comlink.Remote<typeof import('./zkappWorker').api>;

constructor() {
// Initialize the worker from the zkappWorker module
const worker = new Worker(new URL('./zkappWorker.ts', import.meta.url), { type: 'module' });
// Wrap the worker with Comlink to enable direct method invocation
this.remoteApi = Comlink.wrap(worker);
}

async setActiveInstanceToDevnet() {
return this.remoteApi.setActiveInstanceToDevnet();
}

async loadContract() {
return this.remoteApi.loadContract();
}

async compileContract() {
return this.remoteApi.compileContract();
}

async fetchAccount(publicKeyBase58: string) {
return this.remoteApi.fetchAccount(publicKeyBase58);
}

async initZkappInstance(publicKeyBase58: string) {
return this.remoteApi.initZkappInstance(publicKeyBase58);
}

async getNum(): Promise<Field> {
const result = await this.remoteApi.getNum();
return Field.fromJSON(JSON.parse(result as string));
}

async createUpdateTransaction() {
return this.remoteApi.createUpdateTransaction();
}

async proveUpdateTransaction() {
return this.remoteApi.proveUpdateTransaction();
}

async getTransactionJSON() {
return this.remoteApi.getTransactionJSON();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-nocheck
import styles from '@/styles/Home.module.css';
import styles from '../styles/Home.module.css';
import { useEffect, useState, useRef } from 'react';

export default function GradientBG({ children }) {
Expand Down
Loading

0 comments on commit f67c1cc

Please sign in to comment.