Build the app
We're going to install our app dependencies and then build the multi-producer, multi-consumer app.
Install app dependencies
The following instructions assume that you have installed Cubist.
Let's start by installing the Cubist Node.js SDK and related dependencies.
cd
to the application directory of
your chosen example and language. Then:
- npm
- yarn
npm install
yarn
Build instructions
In the cubist-config.json
we specify the target
chains
on which each contract should run:
...
"contracts": {
"root_dir": "contracts",
"targets": {
"avalanche": {
"files": [
"./contracts/Sender1.sol",
"./contracts/Receiver1.sol"
]
},
"ava_subnet" : {
"files": [
"./contracts/Channel.sol"
]
},
"ethereum": {
"files": [
"./contracts/Sender2.sol"
]
},
"polygon": {
"files": [
"./contracts/Receiver2.sol"
]
}
}
}
...
root_dir
tells Cubist the directory in which your contracts live.
To change chains, simply alter targets
to specify different chains
for different files. Note that Cubist only allows you to assign chains
at a per file granularity---not per contract. If you have two contracts
in the same file and want to deploy them on different chains, you'll have to
put each contract in its own file.
Now that we've specified where our contracts should run, we build the project using Cubist:
- JavaScript
- TypeScript
cubist build
- npm
- yarn
cubist build && npm run build
cubist build && yarn build
What's going on behind the scenes
When you invoke build
, Cubist generates new files in the build
directory:
build
├── ava_subnet // Compiled contracts and metadata for everything to be deployed on an Avalanche subnet
├── ava // Compiled contracts and metadata for everything to be deployed on Avalanche
├── ethereum // ...etc...
├── orm // ORM interface for interfacing with the contracts
└── polygon // ...etc...
The orm
directory contains Cubist-generated binding code for interacting
with contracts from JavaScript or TypeScript.
Within each target directory (e.g., ava_subnet
), Cubist saves:
- The ABIs produced when
compiling the contracts with
solc
(within theartifacts
directory) and - The original and shim source files (within the
contracts
directory).
Cubist copies all files from the original contracts
directory into each
target directory; if shims are required on a given target, the
<target>/contracts
directory includes shim files instead of original source files.
Let's look a little closer at the ava_subnet
directory:
ava_subnet
├── artifacts // ABI and metadata directory
| ├── Channel.sol
| | └── Channel.json
| ├── Receiver1.sol
| | └── Receiver1.json
| └── Receiver2.sol
| └── Receiver2.json
├── build_infos // Info for the Cubist tool
├── cache // Cubist build cache
└── contracts // All contracts from the original project, or their shims
├── Channel.sol // Original Channel.sol contract
├── Receiver1.sol // Cubist-generated Receiver1 shim
├── Receiver1.bridge.json // Receiver1 configuration for the Cubist relayer
├── Receiver2.sol // Cubist-generated Receiver2 shim
├── Receiver2.bridge.json // Receiver2 configuration for the Cubist relayer
└── ... // More contracts
The artifacts
directory contains metadata for each contract actually
deployed on the Avalanche subnet. The Channel
contract exists in
the artifacts
directory because the channel is deployed on an Avalanche
subnet. To understand why the Receiver1
and
Receiver2
shims end up on the
subnet, too, let's take a look back at the Channel
code:
import './Receiver1.sol';
import './Receiver2.sol';
contract Channel {
uint256 number;
R1 r1; // deployed on Avalanche
R2 r2; // deployed on Polygon
...
function send(uint256 num) public {
r1.store(num); // cross-chain call
r2.store(num); // cross-chain call
number = num;
}
...
}
Since the Channel
interacts with R1
and R2
, which are both deployed on
different chains, Cubist must generate shims for Receiver1
and Receiver2
for deployment on the Avalanche subnet. These
shims let Channel
call r1.store(num)
and r2.store(num)
; when
they're called, the shim contract emits events that tell the Cubist
off-chain relayer to relay the function calls to the true receiver
contracts on Avalanche and Polygon.
Here's an example of one of the generated shim contracts, the one for
Receiver1
:
contract R1 {
event __cubist_event_R1_store(uint256 num);
...
function store(uint256 num) public onlyCaller {
emit __cubist_event_R1_store(num);
}
}
When Channel.sol calls send
, the shim contract emits the
___cubist_event_R1_store
event, which tells the relayer to invoke
send
on the "real" Receiver1
contract (deployed on Avalanche).
cubist build
also generates the information that lets the Cubist
relayer do its job. That's stored in the contracts
directory, within the
Receiver1.bridge.json
and Receiver2.bridge.json
files.
You should not modify any of the files in the build
directory. These files are automatically generated.