Skip to main content

Run the app

Let's start the chains and relayer and then run our token bridge!

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.

tip

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.

caution

Right now, Avalanche and Avalanche-subnet localnets are slower to start up than other networks. We're working on it!

Run the app

Let's deploy and use our token bridge from Rust (and the command line!). The src/cli.rs file defines command line options for the application: deploy, view balances, buy, and sell. Open up the file to follow along, and we'll walk through the deploy and buy commands in more detail.

(Advanced) deployment

First, let's deploy our TokenSender and ERC20Bridged contracts. This is a little more complex than previous examples, since there's a circular dependency between the two contracts: the TokenSender stores a reference to ERC20Bridged, while ERC20Bridged stores a reference to TokenSender. This circular reference is important because it lets the token bridge "go both ways"; we can use it to both buy and sell native tokens and ERC20 tokens.

One way of handling circular dependencies is to one contract, deploy another with the first contract's address, and then update the first contract to include the second contract's address. We don't have to do that here. Instead, we're going to break the circular dependency by explicitly deploying our contracts and their generated shims separately.

This strategy works because the shims don't hold (circular) references to any other contracts; all they do is emit events when they are called. We can take advantage of the shims' simplicity to successfully deploy all of our contracts in sequence---without having to update them later. Here's the strategy we'll use:

  1. Deploy ERC20Bridged's shim (on Ethereum)
  2. Deploy TokenSender to Ethereum (and its shim to Polygon), giving the constructor the address of ERC20Bridged's shim
  3. Deploy the real ERC20Bridged contract to Polygon, giving it the address of the TokenSender shim on Polygon.

The deploy function follows this strategy:

// Create new Cubist instance
let cubist = cubist().await?;
// Deploy ERC20Bridge shim
let e20b = ERC20Bridged::deploy_shims().await?;
// Get the address of the shim (on Ethereum, TokenSender's target chain)
let e20b_shim_addr = e20b.addr(TokenSender::target());
// Deploy TokenSender with the address of the ERC20Bridged shim (on Ethereum)
// (Also deploys TokenSender shim on Polygon)
let toks = TokenSender::deploy(e20b_shim_addr).await?;
// Get the address of the TokenSender shim (on Polygon, ERC20Bridged's target chain)
let toks_shim_addr = toks.addr(ERC20Bridged::target());
// Deploy ERC20Bridged with the address of the TokenSender shim (on Polygon)
let _e20b =
ERC20Bridged::deploy(("FooBarBaz".to_owned(), "FBB".to_owned(), toks_shim_addr)).await?;

First it deploys the ERC20Bridged shim to Ethereum, then TokenSender (to Ethereum) and its shim (to Polygon), and finally ERC20Bridged (to Polygon).

We can deploy the contracts from the command line with:

cargo run --bin cli deploy

The first line of output reflects that we've deployed TokenSender with constructor argument 0xc059..., which is the address of the ERC20Bridged contract's shim. The second line tells us that that we've deployed ERC20Bridged to swap FBB, and given it constructor argument 0xa1e8..., which is the address of the TokenSender contract's shim. The last two lines result from when_bridged, which checks that the Cubist relayer service is up and running.

Buy

The buy command lets us transfer native (wei) tokens on Ethereum for FBB tokens on Polygon (with a conversion rate of 99.9%). Let's look at the buy command documentation:

/// Mint some FBB tokens.  This is done by calling 'TokenSender' and specifying a WEI amount and
/// an address to receive minted FBB. Conversion rate is 0.999, which means that 'TokenSender'
/// will send a request to 'ERC20Bridged' to mint FBB tokens (in the amount of 99.9% of the
/// received WEI amount) and award them to the specified recipient.
Buy(BuyArgs)

The buy command's arguments are as follows:

struct BuyArgs {
/// Payment in WEI. The amount of minted FBB will be equal to 99.9% of that.
#[clap(index = 1)]
payment_wei: u64,
/// Receiver of newly minted FBB. Either a hex address (starting with '0x')
/// or account index on 'ERC20Bridged' chain. Defaults to the address
/// of the first (index 0) account on 'ERC20Bridged' chain.
#[clap(index = 2, default_value = "0")]
fbb_receiver: String,
}

The buy command is extremely simple:

// Initialize the TokenSender contract from its deployment receipt
let tok = TokenSender::deployed()
.await
.context("Contracts not deployed; call 'deploy' first")?;
// Get one of the pre-created accounts by index
// This is an app specific function defined in cli.rs (and thus not Cubist-generated)
let receiver = to_address(&args.fbb_receiver, erc20_accounts().await?)?;
// Call the TokenSender's bridgeSend function via the Cubist bindings
let mut call = tok.bridge_send(receiver);
// Attach the user-specified amount of wei to the transaction
call.tx.set_value(args.payment_wei);
// Actually make the bridgeSend call
call.send().await?.await?;

All it does is invoke TokenSender's bridgeSend via the Cubist bindings, with the user-specified amount of payment_wei and receiver account. Similar to deploy, we can run the buy command via the command line:

cargo run --bin cli buy 500000000000000000

This will send half an ETH to the default receiver account.

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.