Cross-blueprint Calls

A cross-blueprint call happens when a function/method invokes a function/method defined in a different blueprint whether the blueprint is within the same package our in a different package. This allows a developer to create complex systems by composing various blueprints and components together.

Using a blueprint from the same package

You might decide to combine multiple blueprints in the same package. This allows you to easily deploy complex inter-blueprint functionality to the ledger. In this section, you will learn how to call a blueprint from another one living in the same package.

Let’s say you have two blueprints: CoffeeMachine and AlarmClock. If you want to be able to instantiate a CoffeeMachine component and call its methods from one of the AlarmClock’s method/function you would create three files:

src/lib.rs

// Import the blueprints that are part of the package
mod coffee_machine;
mod alarm_clock;

This lib.rs file is the starting point of all Scrypto packages. If you have only one blueprint in the package, you could write the logic directly in that file, like we saw previously. In our case, we will write the logic of the two blueprints in separate files. That’s why in lib.rs we are importing the two other files to include in our package (coffee_machine and alarm_clock) with the mod keyword.

src/coffee_machine.rs

use scrypto::prelude::*;

#[blueprint]
mod coffeemachine {
    struct CoffeeMachine {}

    impl CoffeeMachine {
        pub fn new() -> Owned<CoffeeMachine> { (1)
            Self{}.instantiate() (2)
        }

        pub fn make_coffee(&self) {
            info!("Brewing coffee !");
        }
    }
}
1 Here we need to return Owned<CoffeeMachine> which is magic syntax that the #[blueprint] macro allows.
2 Also notice that we do not call the globalize() method after instantiation. This is because we want our component to be instantiated as a local or owned component. Having an owned component will only be accessible by our second blueprint that we are going to go through in the next section.

This file includes the logic for the CoffeeMachine blueprint. This blueprint offers a function to instantiate a component with an empty state that offers a make_coffee() method, which we will call from the AlarmClock blueprint.

src/alarm_clock.rs

use scrypto::prelude::*;
use crate::coffee_machine::coffee_machine::*; (1)

#[blueprint]
mod alarm_clock {
    struct AlarmClock {
        // Store the coffee machine component
        coffee_machine: Owned<CoffeeMachine>
    }

    impl AlarmClock {
        pub fn new() -> Global<AlarmClock> {
            Self{
                coffee_machine: CoffeeMachine::new() (2)
            }
            .instantiate()
            .prepare_to_globaize(OwnerRole::None)
            .globalize()
        }

        pub fn try_trigger(&mut self) {
            assert!(Runtime::current_epoch() % 100 == 0, "It's not time to brew yet !");
            self.coffee_machine.make_coffee(); (3)
        }
    }
}
1 Import the CoffeeMachine blueprint
2 Instantiate a CoffeeMachine component from the blueprint
3 Call methods on the component

First, this blueprint imports the CoffeeMachine Blueprint at the top of the file. Then, it instantiates a new CoffeeMachine component and stores it inside a newly instantiated AlarmClock component. Finally, in the try_trigger method, the CoffeeMachine’s make_coffee method is called.

Using a blueprint or component outside of your package

The way to use blueprints from external packages in your local package is through the extern_blueprint! and extern_component! macro and the function signatures. These macros aim to provide a more readable way of importing external blueprints into a package.

With the data provided from extern_blueprint!/extern_component!, the macros generates all of the code required to call the external blueprint. The following is example usage of the macro:

// Define the functions on the Radiswap blueprint
extern_blueprint! {
  "package_sim1p4kwg8fa7ldhwh8exe5w4acjhp9v982svmxp3yqa8ncruad4rv980g"
  GumballMachine {
    fn instantiate_gumball_machine(price: Decimal) -> Global<GumballMachine>;
  }
}

// Define the methods on instantiated components
extern_component! {
  "component_sim1crtkvhxwuff6vk7weufhj9qsd8u7ekajz9zllmqd29mlm8mlxrvsru"
  GumballMachine {
    fn get_price(&self) -> Decimal;
    fn buy_gumball(&mut self mut payment: Bucket) -> (Bucket, Bucket);
  }
}

Once the blueprint package or component is imported we can use the global_component! macro to reference the GumballMachine component.

pub fn proxy_buy_gumball(&self, mut payment: Bucket) => (Bucket, Bucket) {
    let gumball_component: Global<GumballMachine> = global_component!(
        GumballMachine,
        "component_sim1crtkvhxwuff6vk7weufhj9qsd8u7ekajz9zllmqd29mlm8mlxrvsru"
    );

    return gumball_component.buy_gumball(payment)
}

If the external blueprint requires any custom structs or enums, then you can copy their definitions verbatim from the source code for the blueprint, and referenced inside the macro (along with its derives).

Consider a situation where an external blueprint that we wish to import requires a custom enum called DepositResult. The following code shows how this situation can be dealt with when using the extern_component! or extern_blueprint! macros:

#[Derive(ScryptoSbor)]
enum DepositResult { (1)
    Success,
    Failure
}

external_component! {
    CustomAccountComponentTarget {
        fn deposit(&mut self, b: Bucket) -> DepositResult; (1)
        fn deposit_no_return(&mut self, b: Bucket);
        fn read_balance(&self) -> Decimal;
    }
}
1 Since the DepositResult enum is required by this blueprint, it needs to be defined outside of the extern_component! macro, and then used in the function or method signatures. You can obtain the definition of the enum from the source code of the external blueprint.