Skip to content

Commit 6088f99

Browse files
authored
ZkProgram default example starter (#725)
1 parent 01dd76a commit 6088f99

File tree

12 files changed

+187
-59
lines changed

12 files changed

+187
-59
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1717

1818
## Unreleased
1919

20+
### Added
21+
22+
- Adds a new ZkProgram default example that is included with CLI generated projects, coupled with a new example template UI that demonstrates an end to end flow of generating proofs with a ZkProgram, and settling the state on Mina. [#725](https://github.com/o1-labs/zkapp-cli/pull/725)
23+
24+
### Changed
25+
26+
- Add.ts default contract modified to verify and settle ZkProgram proofs. Interact.ts and Add.test.ts modified to generate ZkProgram proofs and settle state on Mina [#725](https://github.com/o1-labs/zkapp-cli/pull/725)
27+
2028
## [0.22.5](https://github.com/o1-labs/zkapp-cli/compare/v0.22.4...v0.22.5) - 2025-02-07
2129

2230
## [0.22.4](https://github.com/o1-labs/zkapp-cli/compare/v0.22.3...v0.22.4) - 2025-01-29

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zkapp-cli",
3-
"version": "0.22.5",
3+
"version": "0.22.6",
44
"description": "CLI to create zkApps (zero-knowledge apps) for Mina Protocol",
55
"homepage": "https://github.com/o1-labs/zkapp-cli/",
66
"repository": {

src/lib/ui/next/customNextPage.js

Lines changed: 82 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ import GradientBG from '../components/GradientBG.js';
66
import styles from '../styles/Home.module.css';
77
import heroMinaLogo from '../public/assets/hero-mina-logo.svg';
88
import arrowRightSmall from '../public/assets/arrow-right-small.svg';
9-
import {fetchAccount, Mina, PublicKey} from "o1js";
10-
import {Add} from "../../contracts";
9+
import {fetchAccount, Mina, PublicKey, Field, Proof} from "o1js";
10+
import { Add, AddZkProgram } from "../../contracts";
1111
1212
// We've already deployed the Add contract on testnet at this address
13-
// https://minascan.io/devnet/account/B62qnTDEeYtBHBePA4yhCt4TCgDtA4L2CGvK7PirbJyX4pKH8bmtWe5
14-
const zkAppAddress = "B62qnTDEeYtBHBePA4yhCt4TCgDtA4L2CGvK7PirbJyX4pKH8bmtWe5";
13+
// https://minascan.io/devnet/account/B62qnfpb1Wz7DrW7279B8nR8m4yY6wGJz4dnbAdkzfeUkpyp8aB9VCp
14+
const zkAppAddress = "B62qnfpb1Wz7DrW7279B8nR8m4yY6wGJz4dnbAdkzfeUkpyp8aB9VCp";
1515
1616
export default function Home() {
1717
const zkApp = useRef<Add>(new Add(PublicKey.fromBase58(zkAppAddress)));
1818
1919
const [transactionLink, setTransactionLink] = useState<string | null>(null);
2020
const [contractState, setContractState] = useState<string | null>(null);
21+
const [zkProgramState, setZkProgramState] = useState<string | null>(null);
22+
const [proof, setProof] = useState<Proof<Field, Field> | null>(null);
2123
const [error, setError] = useState<string | null>(null);
2224
const [loading, setLoading] = useState<boolean>(true);
2325
@@ -28,6 +30,17 @@ export default function Home() {
2830
await fetchAccount({publicKey: zkAppAddress});
2931
const num = zkApp.current.num.get();
3032
setContractState(num.toString());
33+
setZkProgramState(num.toString());
34+
35+
// Compile the AddZkProgram
36+
console.log("Compiling AddZkProgram");
37+
await AddZkProgram.compile();
38+
39+
// Initialize the AddZkProgram with the initial state of the zkapp
40+
console.log("Initialize AddZkProgram with intial contract state of zkapp");
41+
const init = await AddZkProgram.init(num);
42+
setProof(init.proof);
43+
3144
3245
// Compile the contract so that o1js has the proving key required to execute contract calls
3346
console.log("Compiling Add contract to generate proving and verification keys");
@@ -49,18 +62,25 @@ export default function Home() {
4962
await fetchAccount({publicKey: PublicKey.fromBase58(walletKey)});
5063
5164
// Execute a transaction locally on the browser
52-
const transaction = await Mina.transaction(async () => {
53-
console.log("Executing Add.update() locally");
54-
await zkApp.current.update();
55-
});
65+
let hash;
66+
if (proof) {
67+
const transaction = await Mina.transaction(async () => {
68+
console.log("Executing Add.settleState() locally");
69+
await zkApp.current.settleState(proof);
70+
});
5671
57-
// Prove execution of the contract using the proving key
58-
console.log("Proving execution of Add.update()");
59-
await transaction.prove();
72+
// Prove execution of the contract using the proving key
73+
console.log("Proving execution of Add.settleState()");
74+
await transaction.prove();
6075
61-
// Broadcast the transaction to the Mina network
62-
console.log("Broadcasting proof of execution to the Mina network");
63-
const {hash} = await mina.sendTransaction({transaction: transaction.toJSON()});
76+
// Broadcast the transaction to the Mina network
77+
console.log("Broadcasting proof of execution to the Mina network");
78+
({ hash } = await mina.sendTransaction({
79+
transaction: transaction.toJSON()
80+
}));
81+
} else {
82+
throw Error("Proof passed to Add.settleState is null");
83+
}
6484
6585
// display the link to the transaction
6686
const transactionLink = "https://minascan.io/devnet/tx/" + hash;
@@ -82,7 +102,22 @@ export default function Home() {
82102
} finally {
83103
setLoading(false);
84104
}
85-
}, []);
105+
}, [proof]);
106+
107+
const updateZkProgram = useCallback(async () => {
108+
setLoading(true);
109+
110+
if (contractState && proof) {
111+
// Call the AddZkProgram update method
112+
console.log("Calling AddZkProgram.update");
113+
const update = await AddZkProgram.update(Field(contractState), proof);
114+
setProof(update.proof);
115+
setZkProgramState(update.proof.publicOutput.toString())
116+
} else {
117+
throw Error("Proof and or ContractState passed to AddZkProgram.update is null");
118+
}
119+
setLoading(false);
120+
}, [proof]);
86121
87122
return (
88123
<>
@@ -117,20 +152,39 @@ export default function Home() {
117152
Get started by editing
118153
<code className={styles.code}> app/page.tsx</code>
119154
</p>
120-
<div className={styles.state}>
121-
<div>
122-
<div>Contract State: <span className={styles.bold}>{contractState}</span></div>
123-
{error ? (
124-
<span className={styles.error}>Error: {error}</span>
125-
) : (loading ?
126-
<div>Loading...</div> :
127-
(transactionLink ?
128-
<a href={transactionLink} className={styles.bold} target="_blank" rel="noopener noreferrer">
129-
View Transaction on MinaScan
130-
</a> :
131-
<button onClick={updateZkApp} className={styles.button}>Call Add.update()</button>))}
155+
<div className={styles.stateContainer}>
156+
<div className={styles.state}>
157+
<div>
158+
<div>Contract State: <span className={styles.bold}>{contractState}</span></div>
159+
{error ? (
160+
<span className={styles.error}>Error: {error}</span>
161+
) : (loading ?
162+
<div>Loading...</div> :
163+
(transactionLink ?
164+
<a href={transactionLink} className={styles.bold} target="_blank" rel="noopener noreferrer">
165+
View Transaction on MinaScan
166+
</a> :
167+
<button onClick={updateZkApp} className={styles.button}>Add.settleState()</button>))}
168+
</div>
132169
</div>
133-
</div>
170+
<div className={styles.state}>
171+
<div>
172+
<div>
173+
ZkProgram State:{" "}
174+
<span className={styles.bold}>{zkProgramState}</span>
175+
</div>
176+
{error ? (
177+
<span className={styles.error}>Error: {error}</span>
178+
) : loading ? (
179+
<div>Loading...</div>
180+
) : (
181+
<button onClick={updateZkProgram} className={styles.button}>
182+
AddZkProgram.update()
183+
</button>
184+
)}
185+
</div>
186+
</div>
187+
</div>
134188
<div className={styles.grid}>
135189
<a
136190
href="https://docs.minaprotocol.com/zkapps"

src/lib/ui/next/styles/Home.module.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,18 @@
7575
justify-items: center;
7676
}
7777

78+
.stateContainer {
79+
display: flex;
80+
flex-direction: row;
81+
}
82+
7883
.state button {
7984
display: block;
8085
margin: .5rem auto 1rem;
8186
background-color: rgb(255, 255, 255);
8287
border: 1px solid #2d2d2d;
8388
padding: .25rem .5rem;
89+
width: 11.75rem;
8490
cursor: pointer;
8591
}
8692
.state button:hover {
@@ -90,7 +96,7 @@
9096
.state {
9197
padding: 0.8rem 1rem;
9298
gap: 0.5rem;
93-
margin: 1rem;
99+
margin: 1rem 1.6rem;
94100
background-color: rgb(255, 255, 255);
95101
border: 1px solid #2d2d2d;
96102
width: 14rem;
@@ -217,6 +223,10 @@
217223
grid-template-rows: repeat(4, minmax(25%, auto));
218224
}
219225

226+
.stateContainer {
227+
flex-direction: column;
228+
}
229+
220230
.start {
221231
margin-bottom: 2rem;
222232
padding-left: 0.5rem;

src/lib/ui/nuxt/customNuxtIndex.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export default {
126126
127127
// Update this to use the address (public key) for your zkApp account.
128128
// To try it out, you can try this address for an example "Add" smart contract that we've deployed to
129-
// Testnet B62qnTDEeYtBHBePA4yhCt4TCgDtA4L2CGvK7PirbJyX4pKH8bmtWe5.
129+
// Devnet B62qnfpb1Wz7DrW7279B8nR8m4yY6wGJz4dnbAdkzfeUkpyp8aB9VCp.
130130
const zkAppAddress = ''
131131
// This should be removed once the zkAppAddress is updated.
132132
if (!zkAppAddress) {

src/lib/ui/svelte/customPageSvelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default `
1111
1212
// Update this to use the address (public key) for your zkApp account.
1313
// To try it out, you can try this address for an example "Add" smart contract that we've deployed to
14-
// Testnet B62qnTDEeYtBHBePA4yhCt4TCgDtA4L2CGvK7PirbJyX4pKH8bmtWe5 .
14+
// Devnet B62qnfpb1Wz7DrW7279B8nR8m4yY6wGJz4dnbAdkzfeUkpyp8aB9VCp .
1515
const zkAppAddress = ''
1616
// This should be removed once the zkAppAddress is updated.
1717
if (!zkAppAddress) {

templates/project-ts/src/Add.test.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AccountUpdate, Field, Mina, PrivateKey, PublicKey } from 'o1js';
22
import { Add } from './Add';
3+
import { AddZkProgram } from './AddZkProgram';
34

45
/*
56
* This file specifies how to test the `Add` example smart contract. It is safe to delete this file and replace
@@ -8,7 +9,7 @@ import { Add } from './Add';
89
* See https://docs.minaprotocol.com/zkapps for more info.
910
*/
1011

11-
const proofsEnabled = false;
12+
const proofsEnabled = true;
1213

1314
describe('Add', () => {
1415
let deployerAccount: Mina.TestPublicKey,
@@ -20,7 +21,10 @@ describe('Add', () => {
2021
zkApp: Add;
2122

2223
beforeAll(async () => {
23-
if (proofsEnabled) await Add.compile();
24+
await AddZkProgram.compile({ proofsEnabled });
25+
if (proofsEnabled) {
26+
await Add.compile();
27+
}
2428
});
2529

2630
beforeEach(async () => {
@@ -45,23 +49,29 @@ describe('Add', () => {
4549
await txn.sign([deployerKey, zkAppPrivateKey]).send();
4650
}
4751

48-
it('generates and deploys the `Add` smart contract', async () => {
52+
it('initilaizes the `AddZKprogram`', async () => {
4953
await localDeploy();
50-
const num = zkApp.num.get();
51-
expect(num).toEqual(Field(1));
54+
55+
const { proof } = await AddZkProgram.init(Field(1));
56+
57+
expect(proof.publicOutput).toEqual(Field(1));
5258
});
5359

54-
it('correctly updates the num state on the `Add` smart contract', async () => {
60+
it('correctly settles `AddZKprogram` state on the `Add` smart contract', async () => {
5561
await localDeploy();
62+
const initialState = zkApp.num.get();
63+
64+
const init = await AddZkProgram.init(initialState);
65+
const update = await AddZkProgram.update(initialState, init.proof);
5666

57-
// update transaction
67+
// settleState transaction
5868
const txn = await Mina.transaction(senderAccount, async () => {
59-
await zkApp.update();
69+
await zkApp.settleState(update.proof);
6070
});
6171
await txn.prove();
6272
await txn.sign([senderKey]).send();
6373

6474
const updatedNum = zkApp.num.get();
65-
expect(updatedNum).toEqual(Field(3));
75+
expect(updatedNum).toEqual(Field(1));
6676
});
6777
});

templates/project-ts/src/Add.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import { Field, SmartContract, state, State, method } from 'o1js';
2+
import { AddProgramProof } from './AddZkProgram.js';
23

34
/**
45
* Basic Example
56
* See https://docs.minaprotocol.com/zkapps for more info.
67
*
7-
* The Add contract initializes the state variable 'num' to be a Field(1) value by default when deployed.
8-
* When the 'update' method is called, the Add contract adds Field(2) to its 'num' contract state.
8+
* The Add contract verifies a ZkProgram proof and updates a 'num' state variable.
9+
* When the 'settleState' method is called, the Add contract verifies a
10+
* proof from the 'AddZkProgram' and saves the 'num' value to the contract state.
911
*
1012
* This file is safe to delete and replace with your own contract.
1113
*/
1214
export class Add extends SmartContract {
1315
@state(Field) num = State<Field>();
1416

15-
init() {
16-
super.init();
17-
this.num.set(Field(1));
18-
}
19-
20-
@method async update() {
21-
const currentState = this.num.getAndRequireEquals();
22-
const newState = currentState.add(2);
23-
this.num.set(newState);
17+
@method async settleState(proof: AddProgramProof) {
18+
proof.verify();
19+
this.num.requireEquals(proof.publicInput);
20+
const addProgramState = proof.publicOutput;
21+
this.num.set(addProgramState);
2422
}
2523
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ZkProgram, Field, SelfProof } from 'o1js';
2+
3+
export const AddZkProgram = ZkProgram({
4+
name: 'add-program',
5+
publicInput: Field,
6+
publicOutput: Field,
7+
methods: {
8+
init: {
9+
privateInputs: [],
10+
async method(initialState: Field) {
11+
return {
12+
publicOutput: initialState,
13+
};
14+
},
15+
},
16+
17+
update: {
18+
privateInputs: [SelfProof],
19+
async method(
20+
initialState: Field,
21+
previousProof: SelfProof<Field, Field>
22+
) {
23+
previousProof.verify();
24+
initialState.assertEquals(previousProof.publicInput);
25+
return {
26+
publicOutput: previousProof.publicOutput.add(Field(1)),
27+
};
28+
},
29+
},
30+
},
31+
});
32+
33+
export class AddProgramProof extends ZkProgram.Proof(AddZkProgram) {}

templates/project-ts/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import { Add } from './Add.js';
2+
import { AddZkProgram } from './AddZkProgram.js';
23

3-
export { Add };
4+
export { Add, AddZkProgram };

0 commit comments

Comments
 (0)