-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1045 from o1-labs/feature/improve-web-worker-inte…
…rface Improve web worker interface
- Loading branch information
Showing
18 changed files
with
397 additions
and
481 deletions.
There are no files selected for viewing
4 changes: 2 additions & 2 deletions
4
examples/zkapps/04-zkapp-browser-ui/contracts/package-lock.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
54
examples/zkapps/04-zkapp-browser-ui/ui/app/zkappWorkerClient.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
|
||
} |
2 changes: 1 addition & 1 deletion
2
...rowser-ui/ui/src/components/GradientBG.js → ...pp-browser-ui/ui/components/GradientBG.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.