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.
Create or change to a directory where you have write privileges.
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 the12-cross-contract-calls
directory that contains the scaffolding for your project.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.
- First, open the Composability.ts example file.
- 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.