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.

Tutorial 12: Cross Contract Calls

In this tutorial, you learn how smart contracts on a blockchain can interact by calling functions in each other's code, enabling building modular and complex decentralized applications.

Cross contract calls allow smart contracts on a blockchain to interact with each other. This enables the building of complex decentralized applications (Dapps) from multiple modular components. In a cross-contract call, a function in one smart contract can call a function in another smart contract to leverage existing code and functionality.

This tutorial demonstrates passing data between contracts, handling events, and returning values when contracts call each other.

The full example code is provided in the 12-cross-contract-calls/src/ example files.

Prerequisites

  • Make sure you have the latest version of the zkApp CLI installed:

    $ npm install -g zkapp-cli
  • Ensure your environment meets the Prerequisites for zkApp Developer Tutorials.

This tutorial has been tested with:

Create a new project

Now that you have the tooling installed, you can start building your application.

  1. Create or change to a directory where you have write privileges.

  2. Now, create a project using the zk project command:

    $ zk project 12-cross-contract-calls

    As you learned in earlier tutorials, the zk project command creates the 12-cross-contract-calls directory that contains the scaffolding for your project.

  3. Change into the 12-cross-contract-calls directory.

Like all projects, you run zk commands from the root of the 12-cross-contract-calls directory as you work in the src directory on files that contain the TypeScript code for the smart contract.

Each time you make updates, then build or deploy, the TypeScript code is compiled into JavaScript in the build directory.

Prepare the project

Like earlier tutorials, you can prepare your project by deleting the default files that come with the new project and creating a smart contract called Composability.

Write the ZkProgram

Now, the fun part! Write your smart contract in the src/Composability.ts file.

A final version of the smart contract is provided in the Composability.ts example file.

Copy the example

Use the existing code in the Composability.ts example file.

  1. First, open the Composability.ts example file.
  2. Copy the file's entire contents into your project src/Composability.ts file.

Imports and Incrementer smart contract

First, bring in imports and set up the first smart contract Incrementer.

import {
Field,
method,
Mina,
AccountUpdate,
PrivateKey,
SmartContract,
state,
State,
} from 'o1js';

// Contract which adds 1 to a number
class Incrementer extends SmartContract {
@method increment(x: Field): Field {
return x.add(1);
}
}

This Incrementer contract adds 1 to the Field argument, which is passed.

Adder smart contract

Now bring in the second contract Adder that returns the addition of two numbers and adds 1 to the result. The addition of 1 to the result is outsourced to the Incrementer smart contract by creating a new object by passing its address.

// Contract which add two numbers and plus 1 to their sum and return the result
// Incrementing by one is outsourced to Incrementer contract
class Adder extends SmartContract {
@method addPlus1(x: Field, y: Field): Field {
let sum = x.add(y);
let incrementer = new Incrementer(incrementerAddress);
return incrementer.increment(sum);
}
}

Caller smart contract

The final smart contract Caller calls the addPlus1() method of the Adder smart contract and emits the stored result that is returned.

// Contract which calls the Adder contract, stores the result on chain & emits an event
class Caller extends SmartContract {
@state(Field) sum = State<Field>();
events = { sum: Field };

@method callAddAndEmit(x: Field, y: Field) {
let adder = new Adder(adderAddress);
let sum = adder.addPlus1(x, y);
this.emitEvent('sum', sum);
this.sum.set(sum);
}
}

The code to interact with the smart contract:

const doProofs = true;

// Deploy and interact with smart contract locally
let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs });
Mina.setActiveInstance(Local);

// Test account that pays all the fees, and puts additional funds into the zkapp
let feePayerKey = Local.testAccounts[0].privateKey;
let feePayer = Local.testAccounts[0].publicKey;

// The Incrementer contract's address
let incrementerKey = PrivateKey.random();
let incrementerAddress = incrementerKey.toPublicKey();

// The Adder contract's address
let adderKey = PrivateKey.random();
let adderAddress = adderKey.toPublicKey();

// The Caller contract's address
let callerKey = PrivateKey.random();
let callerAddress = callerKey.toPublicKey();

let callerZkapp = new Caller(callerAddress);
let adderZkapp = new Adder(adderAddress);
let incrementerZkapp = new Incrementer(incrementerAddress);

// When doProofs is true, compile contracts to generate prover and verifier keys
if (doProofs) {
console.log('compile (incrementer)');
await Incrementer.compile();
console.log('compile (adder)');
await Adder.compile();
console.log('compile (caller)');
await Caller.compile();
}

// Create transaction to deploy contracts
console.log('deploy');
let tx = await Mina.transaction(feePayer, () => {
AccountUpdate.fundNewAccount(feePayer, 3);
callerZkapp.deploy();
adderZkapp.deploy();
incrementerZkapp.deploy();
});
// Sign all four account updates by
// passing the corresponding private key for the mentioned public addresses
await tx.sign([feePayerKey, callerKey, adderKey, incrementerKey]).send();

// Create transaction to interact with the callAddAndEmit method of Caller contract
console.log('call interaction');
tx = await Mina.transaction(feePayer, () => {
zkapp.callAddAndEmit(Field(5), Field(6));
});
console.log('proving (3 proofs.. can take a bit!)');
await tx.prove();
console.log(tx.toPretty());
await tx.sign([feePayerKey]).send();

console.log('state: ' + zkapp.sum.get());

In this example, you spin up the Mina chain and deploy all three smart contracts locally.

Then you call the callAddAndEmit method from the Caller smart contract, which takes two numbers as arguments, then call the Adder smart contract, which adds these two numbers and passes the result in the Incrementer smart contract, which increments the result by 1.

When run successfully, the state is equal to 12.

Conclusion

Congratulations! You have learned how to implement cross contract calls, allowing smart contracts to interact and unlocking new possibilities for modular blockchain applications.