Skip to main content
info

Please note that zkApp programmability is not yet available on Mina Mainnet, but zkApps can now be deployed to Berkeley Testnet.

On-Chain Values

You can access the current on-chain state of a zkApp account. Similarly, you can access many other on-chain values in a zkApp.

Just to give you an idea, here are two possible use cases:

  • You want to let users vote on a proposal, but only within a specific timespan. To restrict the dates, you can make your zkApp require that the current timestamp lies in a certain range.
  • In DeFi, you might need to compute amounts relative to a balance. For example, paying a yield of 0.001 times the account balance requires you to get the current on-chain balance.

There are two categories of on-chain values:

  • Network: includes the current timestamp, block height, total Mina in circulation, and other network state
  • Account: includes fields and properties of the zkApp account, such as balance, nonce, and delegate

The subfields of both categories are accessible on this.network and this.account in a smart contract. For example, the timestamp on this.network.timestamp has four methods:

this.network.timestamp.get();
this.network.timestamp.assertEquals(timestamp);
this.network.timestamp.assertBetween(lower, upper);

The familiar on-chain state has the same get() and assertEquals() methods. The assertBetween() method has even more power: it allows you to make assert that the timestamp is between lower and upper (inclusive).

Example: restricting timestamps

You can see how to use the assertBetween() method in the voting example that was mentioned earlier. Assume you want to allow voting throughout September 2022. Timestamps are represented as a UInt64 in milliseconds since the UNIX epoch. You can use the JS Date object to easily convert to this representation. In the simplest case, a zkApp could just hard-code the dates:

const startDate = UInt64.from(Date.UTC(2022, 9, 1));
const endDate = UInt64.from(Date.UTC(2022, 10, 1));

class VotingApp extends SmartContract {
// ...

@method vote(...) {
this.network.timestamp.assertBetween(startDate, endDate);
// ...
}
}

A more refined example could store the current start date in an on-chain state variable, which can then be reset by some process that is also encoded by the zkApp.

In addition to using a predefined range, you can also construct a range that depends on another variable, such as the current time. For example, a DEX with an order book could support orders that expire after an hour. In this case, the range would be [now, now + 1h]:

const now = this.network.timestamp.get();
this.network.timestamp.assertBetween(now, now.add(60 * 60 * 1000));

Network reference

For completeness, here is the full list of network states you can use and make assertions about in your zkApp.

All of these fields have a get() and an assertEquals() method. The subset that represents "ordered values" (those that are UInt32 or UInt64) also have assertBetween().

Of course, there's no need to remember this -- just type this.network. and let intelligent code complete guide you!

// current UNIX time in milliseconds, as measured by the block producer
this.network.timestamp.get(): UInt64;
// length of the blockchain, also known as block height
this.network.blockchainLength.get(): UInt32;
// total minted currency measured in units of 1e-9 MINA
this.network.totalCurrency.get(): UInt64;
// slots since genesis / hardfork -- a "slot" is the Mina-native time unit of 3 minutes
this.network.globalSlotSinceGenesis.get(): UInt32;
this.network.globalSlotSinceHardFork.get(): UInt32;
// hash of the snarked ledger -- i.e., the state of Mina included in the blockchain proof
this.network.snarkedLedgerHash.get(): Field;
// minimum window density in our consensus algorithm
this.network.minWindowDensity.get(): UInt32;
// consensus data relevant to the current staking epoch
this.network.stakingEpochData.ledger.hash.get(): Field;
this.network.stakingEpochData.ledger.totalCurrency.get(): UInt64;
this.network.stakingEpochData.epochLength.get(): UInt32;
this.network.stakingEpochData.seed.get(): Field;
this.network.stakingEpochData.lockCheckpoint.get(): Field;
this.network.stakingEpochData.startCheckpoint.get(): Field;
// consensus data relevant to the next, upcoming staking epoch
this.network.nextEpochData.ledger.hash.get(): Field;
this.network.nextEpochData.ledger.totalCurrency.get(): UInt64;
this.network.nextEpochData.epochLength.get(): UInt32;
this.network.nextEpochData.seed.get(): Field;
this.network.nextEpochData.lockCheckpoint.get(): Field;
this.network.nextEpochData.startCheckpoint.get(): Field;

Account reference

Here's the full list of values you can access on the zkApp account. Like the network states, these values have get() and assertEquals(). Balance and nonce also have assertBetween().

// the account balance; this might be nanoMINA or a custom token
this.account.balance.get(): UInt64;
// account nonce -- increases by 0 or 1 in every transaction
this.account.nonce.get(): UInt32;
// the account the zkApp delegates its stake to (default: its own address)
this.account.delegate.get(): PublicKey;
// boolean indicating whether an account is new (= didn't exist before the transaction)
this.account.isNew.get(): Bool;
// boolean indicating whether all 8 on-chain state fields where last changed by a transaction
// authorized by a zkApp proof (as opposed to a signature)
this.account.provedState.get(): Bool;
// hash receipt which includes all prior transaction to an account
this.account.receiptChainHash.get(): Field;

Bailing out

In some rare cases, you might, for whatever reason, want to get() an on-chain value without constraining it to any value. If you try this, o1js throws a helpful error reminding you to use assertEquals() and asserBetween(). As an escape hatch, if you want to get() a value and are really sure you want to not constrain the on-chain value in any way, there's assertNothing() on all of these fields (including on-chain state). Use at your own risk.

danger

assertNothing() should be rarely used and could cause security issues through unexpected behavior if used improperly. Be certain you know what you're doing before using this.

Setting account fields

Just like on-chain state, some account fields can be written to. Again, the API is consistent with state: this.account.<field>.set(newValue).

For example, here's how to change permissions on an account:

this.account.permissions.set({
...Permissions.default(),
setVerificationKey: Permissions.impossible(),
});

And here's how to set the delegate (the account that your smart contract delegates its stake to):

this.account.delegate.set(delegatePublicKey);

The fields that you can set are not the same as the fields that you can make assertions about. Here is the full list of fields that have a .set():

// the account that this account delegates its MINA stake to
this.account.delegate.set(value: PublicKey);
// the verification key
this.account.verificationKey.set(value: VerificationKey);
// account permissions, to control authorization for performing actions on the account
this.account.permissions.set(value: Permissions);
// currently unused - could become URL holding zkApp metadata
this.account.zkappUri.set(value: string);
// token symbol of the token owned by this account — only relevant for token contracts!
this.account.tokenSymbol.set(value: string);
// parameters to control a vesting schedule, used in time-locked accounts
this.account.timing.set(value: Timing);

Accessing accounts other than the zkApp's account

The API described in this section (get / set / assertEquals / ...) can be used to access the zkApp account itself, but also any other account. To do so, create an account update to find the same account / network fields API:

let accountUpdate = AccountUpdate.create(address);

// use the balance of this account
let balance = accountUpdate.account.balance.get();
accountUpdate.account.balance.assertEquals(balance);

// assert that this account is new
accountUpdate.account.isNew.assertEquals(Bool(true));

// set permissions this account
accountUpdate.account.permissions.set(permissions);
tip

When setting fields on an account update, you must ensure that this same account update has the correct authorization to perform those actions. For example, to initially set the verification key, the update requires a signature from the account owner:

// use createSigned to require a signature
let accountUpdate = AccountUpdate.createSigned(address);

// set the verification key on the account; could be used to deploy a zkApp from a zkApp
accountUpdate.account.verificationKey.set(vk);