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:
- Mina zkApp CLI version
0.16.0
- o1js version
0.16.2
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';
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.
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.
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;
let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs });
Mina.setActiveInstance(Local);
let feePayerKey = Local.testAccounts[0].privateKey;
let feePayer = Local.testAccounts[0].publicKey;
let incrementerKey = PrivateKey.random();
let incrementerAddress = incrementerKey.toPublicKey();
let adderKey = PrivateKey.random();
let adderAddress = adderKey.toPublicKey();
let zkappKey = PrivateKey.random();
let zkappAddress = zkappKey.toPublicKey();
let zkapp = new Caller(zkappAddress);
let adderZkapp = new Adder(adderAddress);
let incrementerZkapp = new Incrementer(incrementerAddress);
if (doProofs) {
console.log('compile (incrementer)');
await Incrementer.compile();
console.log('compile (adder)');
await Adder.compile();
console.log('compile (caller)');
await Caller.compile();
}
console.log('deploy');
let tx = await Mina.transaction(feePayer, () => {
AccountUpdate.fundNewAccount(feePayer, 3);
zkapp.deploy();
adderZkapp.deploy();
incrementerZkapp.deploy();
});
await tx.sign([feePayerKey, zkappKey, adderKey, incrementerKey]).send();
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.