Skip to main content
Composability — how one contract can install and call into another. This is the first of two chapters. Here you wire simple-token into my-token until everything compiles. The next chapter makes the cross-contract call actually run, then tests it. Composability: my-token installing simple-token holders Putting it together: incrementer and receiver each install a simple-token holder, and increment() also transfers a token.

Goal

Make my-token’s incrementer and receiver each hold simple-tokens. Whenever increment() fires, also transfer one token from incrementer to receiver. Across the two chapters we’ll:
  • Externalize simple-token’s self_refs as contract refs / instances (this chapter)
  • Add simple-token as a dependency of my-token (this chapter)
  • Install simple-token holders inside the my-token components (this chapter)
  • Call their functions across contracts, then test it (next chapter)
Each phase below ends with a checkpoint. If a phase’s checkpoint doesn’t pass, fix it before moving on — the later phases build directly on it.

Phase 1 · Make simple-token reusable

First, externalize simple-token’s self refs so other contracts can address it. Add to simple-token/simple-token/Cargo.toml:
[package.metadata.genie]
implementation = { code_id = "0x759bba8a4baf89c4f035107f092526ad17bc11f4712c9b8f221be74a3d663ce4::1" }

[package.metadata.genie.instances]
alpha = { contract = "0x759bba8a4baf89c4f035107f092526ad17bc11f4712c9b8f221be74a3d663ce4::1" }
beta  = { contract = "0x759bba8a4baf89c4f035107f092526ad17bc11f4712c9b8f221be74a3d663ce4::2" }
The generic implementation lives at one entity ID; alpha and beta are distinct instances (think USDC vs USDT) sharing the same ABI but handling different tokens.
Checkpoint 1gen genie build simple-token/simple-token succeeds and the generated instance::alpha / instance::beta paths exist.

Phase 2 · Wire the dependency through root

Add simple-token as a dependency

In my-token/Cargo.toml under [workspace.dependencies]:
simple-token = { path = "../simple-token/simple-token" }
And in my-token/my-token/Cargo.toml under [dependencies]:
simple-token = { workspace = true }
Now you can use it from my_token_contracts.rs:
use simple_token::simple_token_contract::instance::alpha::Holder;

Pass the simple-token contract through root

Installing a third-party component currently requires a GvmContract. Store it on root, accept it in deploy(), and hand it to components via installation_request().
pub struct MyTokenRoot {
    value: u64,
    simple_token_contract: GvmContract,
}
#[deploy]
pub async fn deploy(simple_token_contract: GvmContract) -> Result<Self> {
    Ok(Self { value: 0, simple_token_contract })
}

#[installation_request]
pub async fn installation_request(
    &mut self,
    _component_type: MyTokenComponents,
) -> Result<GvmContract> {
    Ok(self.simple_token_contract)
}
Each component’s install() now receives a GvmContract:
pub async fn install(installation_return_value: GvmContract) -> Result<Self> {
    // ...
}
Checkpoint 2my-token builds with the new deploy / installation_request / install signatures, and use simple_token::…::alpha::Holder resolves.

Phase 3 · Install a holder inside each component

use simple_token::simple_token_contract::simple_token::SimpleTokenClient;
use simple_token::simple_token_contract::instance::alpha::Holder;

pub struct CounterIncrementer {
    incrementer_simple_token_holder: Holder,
}

pub struct CounterReceiver {
    counter: u128,
    receiver_simple_token_holder: Holder,
}
#[install]
pub async fn install(installation_return_value: GvmContract) -> Result<Self> {
    let incrementer_simple_token_holder = SimpleTokenClient::installer()
        .holder.install(installation_return_value).await?
        .try_into()?;
    Ok(Self { incrementer_simple_token_holder })
}
Checkpoint 3gen genie build my-token is green; each my-token component stores its own simple-token holder. You haven’t called across contracts yet — that’s the next chapter.
my-token/src/my_token_contracts.rs
use genie::contract;

/// Implementation of the MyToken contract
#[contract]
pub mod my_token {
    /// Root component for MyToken
    pub mod root {
        use genie::{GvmContract, Result, deploy, installation_request, owner, view};

        /// Root implementation for MyToken
        pub struct MyTokenRoot {
            value: u64,
            simple_token_contract: GvmContract,
        }

        impl MyTokenRoot {

            /// Handles installation requests for MyToken components.

            #[deploy]
            pub async fn deploy(simple_token_contract: GvmContract) -> Result<Self> {
                Ok(Self { value: 0, simple_token_contract })
            }

            #[installation_request]
            pub async fn installation_request(
                &mut self,
                _component_type: MyTokenComponents,
            ) -> Result<GvmContract> {
                Ok(self.simple_token_contract)
            }

            /// Sets the stored value (owner-only activation)
            #[owner]
            pub fn set_value(&mut self, new_value: u64) -> Result<()> {
                self.value = new_value;
                Ok(())
            }

            /// Gets the stored value (view function)
            #[view]
            pub fn get_value(&self) -> Result<u64> {
                Ok(self.value)
            }
        }
    }

    /// incrementer component for MyToken
    pub mod incrementer {
        use super::self_refs::Receiver;

        use genie::GvmContract;
        use genie::{GvmError, Result, install, owner, private};
        use simple_token::simple_token_contract::instance::alpha::Holder;
        use simple_token::simple_token_contract::simple_token::SimpleTokenClient;

        /// Incrementer implementation
        pub struct CounterIncrementer {
            incrementer_simple_token_holder: Holder,
        }

        impl CounterIncrementer {
            /// Install handler for Incrementer
            #[install]
            pub async fn install(installation_return_value: GvmContract) -> Result<Self> {
                let incrementer_simple_token_holder = SimpleTokenClient::installer()
                    .holder.install(installation_return_value).await?
                    .try_into()?;
                Ok(Self { incrementer_simple_token_holder })
            }

            #[owner]
            pub async fn increment(&mut self, to: Receiver, with_confirmation: bool) -> Result<()> {
                let receive_future = to.remote.receive();
                if with_confirmation {
                    if let Err(increment_result) = receive_future.await {
                        //Perform an action that reflects the action failed +
                        //Throw an error that accurately describes what happened
                        return self.handle_increment_failure(increment_result);
                    } else {
                        //Action succeeded so you can update something
                    };
                } else {
                    receive_future
                        .spawn()
                        .on_error(Self::callbacks().handle_increment_failure);
                }
                Ok(())
            }

            #[owner]
            pub async fn increment_same_entity(&mut self, to: Receiver) -> Result<()> {
                if let Err(increment_result) = to.local.receive() {
                    return self.handle_increment_failure(increment_result);
                }
                Ok(())
            }

            #[private]
            pub fn handle_increment_failure(&mut self, delivery_error: GvmError) -> Result<()> {
                //There's nothing meaningful to fix so throw an error
                Err(delivery_error.into())
            }
        }
    }

    /// Receiver component for MyToken
    pub mod receiver {
        use genie::GvmContract;
        use genie::{Result, install, private, view};
        use simple_token::simple_token_contract::instance::alpha::Holder;
        use simple_token::simple_token_contract::simple_token::SimpleTokenClient;

        /// Receiver implementation
        pub struct CounterReceiver {
            counter: u128,
            receiver_simple_token_holder: Holder,
        }

        impl CounterReceiver {
            /// Install handler for CounterReceiver
            #[install]
            pub async fn install(simple_token_contract: GvmContract) -> Result<Self> {
                let receiver_simple_token_holder = SimpleTokenClient::installer()
                    .holder.install(simple_token_contract).await?
                    .try_into()?;
                Ok(Self { counter: 0, receiver_simple_token_holder })
            }

            #[view]
            pub fn get_count(&self) -> Result<u128> {
                Ok(self.counter)
            }

            #[private]
            pub fn receive(&mut self) -> Result<()> {
                self.counter += 1;
                Ok(())
            }
        }
    }
}

What’s next

Borrowed Code · Make It Run

Default routing, the cross-contract transfer() call inside increment(), and the test that proves it works.