Skip to main content

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.