Run the app
As usual, let's start the chains and relayer, and then interact with the contracts.
Start Cubist chains and relayer
Now that we've built our contracts, we want to deploy them and then interact
with them. We're going to do this locally; the config (cubist-config.json
)
already specifies the URLs on which the chains will run.
The app config will look something like this:
{
...
"network_profiles": {
"default": {
"ethereum": { "url": "http://127.0.0.1:8545" },
"polygon": { "url": "http://127.0.0.1:9545" }
}
}
}
You can alter the config to specify other URLs if you choose.
To start both the local chains and the Cubist relayer, use:
cubist start
Note that this command may take a while (i.e., a minute) the first time you run it. This is because Cubist needs to download and build the local chain running services before it can start the chains.
Once you've run cubist start
, you'll see output that looks something like this:
Launching chains
ethereum ✔ [ 0s] [....................] http://localhost:8545/
polygon ✔ [ 1s] [....................] http://localhost:9545/ All servers available
Watching <path>/cubist-deploy dir
This output shows where the localnets are running (e.g., localhost:8545
),
and how long they took to become initialized and available. The output
also lets us know that the local relayer is successfully watching for the
events that tell it to relay information from one chain to another.
Alternatively, you can start the chains and the relayer seperately with
cubist chains start
and cubist relayer start
. For more information on
these and other Cubist CLI commands, try cubist help
, or check out the
CLI reference.
Right now, Avalanche and Avalanche-subnet localnets are slower to start up than other networks. We're working on it!
Run the app
Deploy
Let's deploy the contracts and the generated shims:
- npm
- yarn
npm run deploy
> [email protected] deploy
> node ./src/deploy.js
Bridging R1::store (0xaddr@ava_subnet -> 0xaddr@avalanche)
Deployed consumers/receivers
R1 to avalanche @ 0xaddr
R2 to polygon @ 0xaddr
Bridging R2::store (0xaddr@ava_subnet -> 0xaddr@polygon)
Deployed Channel to ava_subnet @ 0xaddr
Deploy producers
Bridging Channel::send (0xaddr@avalanche -> 0xaddr@ava_subnet)
Bridging Channel::send (0xaddr@ethereum -> 0xaddr@ava_subnet)
Deployed producers/senders
S1 to avalanche @ 0xaddr
S2 to ethereum @ 0xaddr
yarn deploy
yarn run v1.22.19
$ node ./src/deploy.js
Bridging R1::store (0xaddr@ava_subnet -> 0xaddr@avalanche)
Deployed consumers/receivers
R1 to avalanche @ 0xaddr
R2 to polygon @ 0xaddr
Bridging R2::store (0xaddr@ava_subnet -> 0xaddr@polygon)
Deployed Channel to ava_subnet @ 0xaddr
Deploy producers
Bridging Channel::send (0xaddr@avalanche -> 0xaddr@ava_subnet)
Bridging Channel::send (0xaddr@ethereum -> 0xaddr@ava_subnet)
Deployed producers/senders
S1 to avalanche @ 0xaddr
S2 to ethereum @ 0xaddr
✨ Done in 37.90s.
The core of the deploy script that npm run deploy
invokes is this simple function (which uses the ORM Cubist generated
at build time):
import { CubistORM, } from '../build/orm/index.js';
// Project instance
const cubist = new CubistORM();
// Contract factories
const R1 = cubist.R1;
const R2 = cubist.R2;
const Channel = cubist.Channel;
const S1 = cubist.S1;
const S2 = cubist.S2;
export async function deploy() {
// Deploy both receivers
const r1 = await R1.deploy();
const r2 = await R2.deploy();
...
// Deploy the Channel with the address of both receiver shims
let ch = await Channel.deploy(r1.addressOn(Channel.target()), r2.addressOn(Channel.target()));
...
// Deploy the first sender with the address of the Channel's shim
// on the sender's target (configurable; currently Avalance)
const s1 = await S1.deploy(ch.addressOn(S1.target()));
// Deploy the second sender with the address of the Channel's shim
// on the sender's target (configurable; currently Ethereum)
const s2 = await S2.deploy(ch.addressOn(S2.target()));
...
}
First, the code uses the Cubist instance to deploy both receivers.
Then it deploys the Channel
:
let ch = await Channel.deploy(r1.addressOn(Channel.target()), r2.addressOn(Channel.target()));
The first constructor argument gets the address of R1
on the
Avalanche subnet (since Channel.target()
is the Avalanche subnet)---this
means the address of the Cubist-generated R1
shim. The second
constructor argument does the same, also returning the address of the
R2
shim on the subnet. If we'd tried to get the address of S1
on
Channel.target
, the call would have failed: since the Channel
holds
no reference to the sender contracts, Cubist never generates an S1
shim
on the Avalanche subnet.
Next, the deploy script deploys the sender contracts on their targets, Avalanche and Ethereum:
const s1 = await S1.deploy(ch.addressOn(S1.target()));
const s2 = await S2.deploy(ch.addressOn(S2.target()));
S1
's constructor takes the address of the Channel's shim on Avalanche,
and S2
's constructor takes the address of the Channel's shim
on Ethereum.
Interact
Now that we've deployed the contracts, we can use them to send data across the channel, from either sender to:
- npm
- yarn
npm run send 1 5
> [email protected] send
> node ./src/send.js
Sending on channel 1 (0xaddr, avalanche) 5
sending Channel::send(5) (0xaddr@avalanche -> 0xaddr@ava_subnet)
sending R1::store(5) (0xaddr@ava_subnet -> 0xaddr@avalanche)
sending R2::store(5) (0xaddr@ava_subnet -> 0xaddr@polygon)
SENT Channel::send(5) (0xaddr@avalanche -> 0xaddr@ava_subnet)
SENT R2::store(5) (0xaddr@ava_subnet -> 0xaddr@polygon)
SENT R1::store(5) (0xaddr@ava_subnet -> 0xaddr@avalanche)
yarn send 1 5
yarn run v1.22.19
$ node ./src/send.js 1 5
Sending on channel 1 (0xaddr, avalanche) 5
sending Channel::send(5) (0xaddr@avalanche -> 0xaddr@ava_subnet)
sending R1::store(5) (0xaddr@ava_subnet -> 0xaddr@avalanche)
sending R2::store(5) (0xaddr@ava_subnet -> 0xaddr@polygon)
SENT Channel::send(5) (0xaddr@avalanche -> 0xaddr@ava_subnet)
SENT R2::store(5) (0xaddr@ava_subnet -> 0xaddr@polygon)
SENT R1::store(5) (0xaddr@ava_subnet -> 0xaddr@avalanche)
✨ Done in 4.84s.
This command invokes the send
script, which looks like this:
import { CubistORM, } from '../build/orm/index.js';
// Project instance
const cubist = new CubistORM();
const S1 = cubist.S1;
const S2 = cubist.S2;
export async function send(ch, val) {
if (!await cubist.whenBridged()) {
throw new Error('Bridge not running');
}
let sender;
// Choose which sender to use
if (ch === 1) {
// Load from deployment receipt
sender = await S1.deployed();
} else if (ch === 2) {
sender = await S2.deployed();
} else {
throw new Error('Sending channel should be 1 or 2');
}
...
await (await sender.inner.send(val)).wait(/* confirmations: */ 1);
}
The send
script takes two command line arguments: (1) the number to be sent
and (2) the sender to use, either 1 for S1
or 2 for S2
. The send
JavaScript function first loads S1
or S2
from its deployment
receipt, and then calls the smart contract send
function on the correct
sender contract.
Shut down
Running cubist stop
will shut down both the chains and the relayer. Once the
chains and the relayer are shut down, trying to call contract functions from
within your dapp will result in errors.