Building a Blended App
Introduction
This guide provides detailed instructions on how to build a blended HelloWorld application on Fluent. It combines a Rust smart contract to print “Hello” and a Solidity smart contract to print “World.”
This setup demonstrates:
composability between different programming languages (Solidity and Rust)
and interoperability between different virtual machine targets (EVM and Wasm)
within a single execution environment.
Prerequisites
Ensure you have the following installed:
Node.js and npm
Rust and Cargo
Hardhat
pnpm (install via npm:
npm install -g pnpm)
NOTE: you can setting up your first blended app with gblend init cli as well!
Install Fluent Scaffold CLI Tool
To install the Fluent scaffold CLI tool, run the following command in your terminal:
To create a project, run the following in your terminal:
Step 1: Initialize Your Rust Project
1.1 Set Up the Rust Project
1.2 Configure the Rust Project
Cargo.toml
1.3 Write the Rust Smart Contract
src/lib.rs
Detailed Code Explanation
1. #![cfg_attr(target_arch = "wasm32", no_std)]
This line is a compiler directive. It specifies that if the target architecture is wasm32 (WebAssembly 32-bit), the code should be compiled without the standard library (no_std). This is necessary for WebAssembly, which doesn't have a full standard library available.
2. extern crate alloc; and extern crate fluentbase_sdk;
These lines declare external crates (libraries) that the code depends on.
allocis a core library that provides heap allocation functionality.fluentbase_sdkis the SDK provided by Fluent for writing contracts.
3. use alloc::string::{String, ToString};
This line imports the String and ToString types from the alloc crate. This is necessary because the standard std library, which normally includes these, is not available in no_std environments.
4. use fluentbase_sdk::{ basic_entrypoint, derive::{router, function_id, Contract}, SharedAPI };
This line imports various items from the fluentbase_sdk crate:
basic_entrypointis a macro for defining the main entry point of the contract.routerandfunction_idare macros for routing function calls and defining function signatures.ContractTrait enabling contract functionality.SharedAPIis a trait that abstracts the API shared between different environments.
5. #[derive(Contract)] struct ROUTER;
This line defines a struct named ROUTER and derives a contract implementation for it. The ROUTER struct will implement the logic for our contract.
6. pub trait RouterAPI { fn greeting(&self) -> String; }
This defines a trait named RouterAPI with a single method greeting. This method returns a String.
7. #[router(mode = "solidity")] impl<SDK: SharedAPI> RouterAPI for ROUTER<SDK> { ... }
This block implements the RouterAPI trait for the ROUTER struct. The #[router(mode = "solidity")] attribute indicates that this implementation is for a Solidity-compatible router.
Inside the Implementation:
#[function_id("greeting()"]specifies the function signature in Solidity syntax. This tells the router how to call this function from Solidity.fn greeting<SDK: SharedAPI>(&self) -> String { "Hello".to_string() }is the implementation of thegreetingmethod, which simply returns the string "Hello".
8. impl<SDK: SharedAPI> ROUTER<SDK> { fn deploy(&self) { // any custom deployment logic here } }
This block provides an additional method deploy for the ROUTER struct. This method can include custom deployment logic. Currently, it's an empty placeholder.
9. basic_entrypoint!(ROUTER);
This macro invocation sets up the ROUTER struct as the main entry point for the contract. It handles necessary boilerplate code for contract initialization and invocation.
Summary
This Rust code defines a smart contract that will be compiled to WebAssembly. The contract implements a single function greeting that returns the string "Hello". The contract is designed to be called from a Solidity environment, showcasing interoperability between different virtual machines. The basic_entrypoint! macro ties everything together, making ROUTER the entry point for the contract.
1.4 Build the Wasm Project
Run:
Step 2: Initialize Your Solidity Project
2.1 Create Your Project Directory
2.2 Install Dependencies
2.3 Configure TypeScript and Hardhat
2.3.1 Update Hardhat Configuration
hardhat.config.ts
2.3.2 Update package.json
package.jsonpackage.json
2.4 Set Up Environment Variables
Create a .env file:
Replace
your-private-key-herewith your actual private key.
2.5 Write the Solidity Contracts
In this section, we'll create two Solidity smart contracts: IFluentGreeting and GreetingWithWorld.
The interface contract allows the Solidity contract to call the Rust function, demonstrating interoperability between Solidity and Rust within a single execution environment.
The final GreetingWithWorld contract provides a composable solution that combines the outputs of both the Rust and Solidity contracts.
Create a
contractsdirectory and add the following:
2.5.1 Define the Interface
contracts/IFluentGreeting.sol
Detailed Code Explanation
Interface Definition:
The IFluentGreeting interface declares a single function greeting() that is external and viewable, meaning it does not modify the state of the blockchain and returns a string. This function will be implemented by another contract and is used to interact with the Rust smart contract.
Interaction with Rust Code:
The greeting function defined in this interface matches the Rust function that returns a greeting message. The Solidity interface allows the Solidity contract to call the Rust smart contract's function.
2.5.2 Implement the Greeting Contract
contracts/GreetingWithWorld.sol
Detailed Code Explanation
Import Statement: Imports the IFluentGreeting interface defined earlier.
Contract Definition: Defines a contract GreetingWithWorld.
State Variable: Declares a state variable fluentGreetingContract of type IFluentGreeting. This variable will hold the address of the deployed Rust smart contract.
Constructor:
Takes an address
_fluentGreetingContractAddressas a parameter.Initializes the
fluentGreetingContractwith the provided address.Function
getGreeting:Calls the
greetingfunction of thefluentGreetingContractto get the greeting message from the Rust contract.Concatenates the greeting message with ", World" using
abi.encodePackedand returns the resulting string.
Interaction with Rust Code:
The
GreetingWithWorldcontract interacts with the Rust smart contract by calling thegreetingfunction via theIFluentGreetinginterface.When
getGreetingis called, it fetches the greeting message ("Hello") from the Rust contract, concatenates it with ", World", and returns the complete greeting ("Hello, World").
How Solidity and Rust Interact:
Rust Smart Contract Deployment: The Rust smart contract is compiled to Wasm and deployed to the blockchain. It contains a function that returns the greeting "Hello".
Solidity Interface (
IFluentGreeting): The Solidity interface declares agreetingfunction that matches the function in the Rust contract.Solidity Implementation (
GreetingWithWorld):The
GreetingWithWorldcontract uses theIFluentGreetinginterface to interact with the Rust contract.It initializes with the address of the deployed Rust contract.
It calls the
greetingfunction of the Rust contract to fetch the greeting message.It concatenates the Rust greeting with ", World" and returns the result.
Step 3: Deploy Both Contracts Using Hardhat
3.1 Create the Deployment Script
This deployment script is responsible for deploying both the Rust smart contract (compiled to Wasm) and the Solidity smart contract (GreetingWithWorld).
deploy/01_deploy_contracts.ts
3.2 Create the Hardhat Task
tasks/get-greeting.ts
3.3 Compile and Deploy the Contracts
Run the following commands to compile and deploy your contracts:
Last updated