Method Input Types
zkApp methods can accept various o1js types as parameters, enabling rich data handling while maintaining zero-knowledge properties. Understanding how to use provable inputs effectively is key to building sophisticated zkApps.
Basic Provable Types
The fundamental o1js types can be used as method parameters:
export class BasicInputsContract extends SmartContract {
@state(Field) value = State<Field>();
@state(Bool) isActive = State<Bool>();
@state(UInt32) counter = State<UInt32>();
init() {
super.init();
this.value.set(Field(0));
this.isActive.set(Bool(true));
this.counter.set(UInt32.from(0));
}
@method async updateValue(newValue: Field) {
// Field input - most common provable type
newValue.assertGreaterThan(Field(0));
this.value.set(newValue);
}
@method async toggleActive(shouldActivate: Bool) {
// Bool input for binary conditions
const currentActive = this.isActive.getAndRequireEquals();
// Conditional logic with Bool
const newState = Provable.if(shouldActivate, Bool(true), Bool(false));
this.isActive.set(newState);
}
@method async incrementBy(amount: UInt32) {
// UInt32 input for bounded integers
amount.assertLessThanOrEqual(UInt32.from(100)); // Max increment of 100
const currentCounter = this.counter.getAndRequireEquals();
const newCounter = currentCounter.add(amount);
this.counter.set(newCounter);
}
}
Core types include Field
, Bool
, UInt32
/UInt64
, PublicKey
, and Signature
. For a refresher on these types, see: Basic Types.
Custom Struct Inputs
Create complex data structures using Struct
:
export class UserData extends Struct({
id: Field,
age: UInt32,
isVerified: Bool,
publicKey: PublicKey,
}) {
// Custom validation method
validate() {
this.age.assertGreaterThan(UInt32.from(0));
this.age.assertLessThan(UInt32.from(150));
this.id.assertGreaterThan(Field(0));
}
}
export class UserRegistrationContract extends SmartContract {
@state(Field) totalUsers = State<Field>();
@state(Field) userHash = State<Field>(); // Hash of all user data
init() {
super.init();
this.totalUsers.set(Field(0));
this.userHash.set(Field(0));
}
@method async registerUser(userData: UserData) {
// Validate the struct input
userData.validate();
// Additional business logic
userData.isVerified.assertTrue(); // Only verified users
// Update state
const currentTotal = this.totalUsers.getAndRequireEquals();
this.totalUsers.set(currentTotal.add(1));
// Hash user data for commitment
const currentHash = this.userHash.getAndRequireEquals();
const userDataHash = Poseidon.hash(userData.toFields());
const newHash = Poseidon.hash([currentHash, userDataHash]);
this.userHash.set(newHash);
}
@method async updateUserData(oldData: UserData, newData: UserData) {
// Both old and new data as struct inputs
oldData.validate();
newData.validate();
// Verify same user (same ID and public key)
oldData.id.assertEquals(newData.id);
oldData.publicKey.assertEquals(newData.publicKey);
// Allow age updates but verify they make sense
newData.age.assertGreaterThanOrEqual(oldData.age);
// Update hash (remove old, add new)
const currentHash = this.userHash.getAndRequireEquals();
const oldHash = Poseidon.hash(oldData.toFields());
const newHash = Poseidon.hash(newData.toFields());
// This is simplified - in practice you'd need a more complex hash tree
const updatedHash = Poseidon.hash([currentHash, oldHash, newHash]);
this.userHash.set(updatedHash);
}
}
Structs provide type safety, reusability, validation, and automatic serialization.