Get Started with Lilypad v0 - Call off-chain distributed compute from your smart contract!
Prefer Video?
Note: Since this video was released some changes have been made to the underlying code, but the process and general architecture remains the same.
Quick Start Guide
The Lilypad Contracts are not currently importable via npm (though this is in progress), so to import them to you own project, you'll need to use their github links
Using Lilypad in your own solidity smart contract requires the following steps
Create a contract that implements the LilypadCaller interface.
As part of this interface you need to implement 2 functions:
lilypadFulfilled - a callback function that will be called when the job completes successfully
lilypadCancelled - a callback function that will be called when the job fails
To trigger a job from your contract, you need to call the LilypadEvents contract which the Lilypad bridge is listening to and which connects to the Bacalhau public network. Create an instance of LilypadEvents by passing the public contract address on the network you are using (see Deployed Network Details) to the LilypadEvents constructor.
Call the LilypadEvents contract function runLilypadJob() passing in the following parameters.
Implement the LilypadCaller Interface in your contract
Create a contract that implements LilypadCallerInterface. As part of this interface you need to implement 2 functions:
lilypadFulfilled - a callback function that will be called when the job completes successfully
lilypadCancelled - a callback function that will be called when the job fails
/** === LilypadCaller Interface === **/pragmasolidity >=0.8.4; import 'https://github.com/bacalhau-project/lilypad/blob/main/hardhat/contracts/LilypadCallerInterface.sol' //Location of file link
/** === User Contract Example === **/contractMyContractisLilypadCallerInterface {functionlilypadFulfilled(address_from,uint_jobId,LilypadResultType_resultType,stringcalldata_result) externaloverride {// Do something when the LilypadEvents contract returns // results successfully }functionlilypadCancelled(address_from,uint_jobId,stringcalldata_errorMsg) externaloverride {// Do something if there's an error returned by the// LilypadEvents contract } }
There are several public examples you can try out without needing to know anything about Docker or WASM specification jobs -> see the Bacalhau Docs. The full specification for Bacalhau jobs can be seen here.
Bacalhau operates by executing jobs within containers. This means it is able to run any arbitrary Docker jobs or WASM images
We'll use the public Stable Diffusion Docker Container located here for this example.
Here's an example JSON job specification for the Stable Diffusion job:
You can do this by either passing it into your constructor or setting it as a variable
// SPDX-License-Identifier: MITpragmasolidity >=0.8.4;import"https://github.com/bacalhau-project/lilypad/blob/main/hardhat/contracts/LilypadEventsUpgradeable.sol";import"https://github.com/bacalhau-project/lilypad/blob/main/hardhat/contracts/LilypadCallerInterface.sol";/** === User Contract Example === **/contractMyContractisLilypadCallerInterface { address public bridgeAddress; // LilypadEvents contract address for interacting with the deployed LilypadEvents contract
LilypadEventsUpgradeable bridge; // Instance of the LilypadEvents Contract to interact withuint256public lilypadFee; //=30000000000000000 on FVM;constructor(address_bridgeContractAddress) { bridgeAddress = _bridgeContractAddress; //the LilypadEvents contract address for your network bridge = LilypadEventsUpgradeable(_bridgeContractAddress); //create an instance of the Events Contract to interact with
uint fee = bridge.getLilypadFee(); // you can fetch the fee amount required for the contract to run also lilypadFee = fee; }functionlilypadFulfilled(address_from,uint_jobId,LilypadResultType_resultType,stringcalldata_result) externaloverride {// Do something when the LilypadEvents contract returns // results successfully }functionlilypadCancelled(address_from,uint_jobId,stringcalldata_errorMsg) externaloverride {// Do something if there's an error returned by the Lilypad Job }}
Call the LilypadEvents runLilypadJob function
Using the LilypadEvents Instance, we can now send jobs to the Bacalhau Network via our contract using the runLilypadJob() function.
Note that calling the runLilypadJob() function requires a network fee. While the Bacalhau public Network is currently free to use, gas fees are still needed to return the results of the job performed. This is the payable fee in the contract.
// SPDX-License-Identifier: MITpragmasolidity >=0.8.4;import"https://github.com/bacalhau-project/lilypad/blob/main/hardhat/contracts/LilypadEventsUpgradeable.sol";import"https://github.com/bacalhau-project/lilypad/blob/main/hardhat/contracts/LilypadCallerInterface.sol";/** === User Contract Example === **/contractMyContractisLilypadCallerInterface {addresspublic bridgeAddress; // Variable for interacting with the deployed LilypadEvents contract LilypadEventsUpgradeable bridge;uint256public lilypadFee; //=30000000000000000;constructor(address_bridgeContractAddress) { bridgeAddress = _bridgeContractAddress; bridge =LilypadEventsUpgradeable(_bridgeContractAddress);uint fee = bridge.getLilypadFee(); // you can fetch the fee amount required for the contract to run also lilypadFee = fee; }//** Define the Bacalhau Specification */stringconstant specStart ='{''"Engine": "docker",''"Verifier": "noop",''"PublisherSpec": {"Type": "estuary"},''"Docker": {''"Image": "ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1",''"Entrypoint": ["python", "main.py", "--o", "./outputs", "--p", "';stringconstant specEnd ='"]},''"Resources": {"GPU": "1"},''"Outputs": [{"Name": "outputs", "Path": "/outputs"}],''"Deal": {"Concurrency": 1}''}';/** Call the runLilypadJob() to generate a stable diffusion image from a text prompt*/functionStableDiffusion(stringcalldata_prompt) externalpayable {require(msg.value >= lilypadFee,"Not enough to run Lilypad job");// TODO: spec -> do proper json encoding, look out for quotes in _promptstringmemory spec =string.concat(specStart, _prompt, specEnd);uint id = bridge.runLilypadJob{value: lilypadFee}(address(this), spec,uint8(LilypadResultType.CID));require(id >0,"job didn't return a value"); prompts[id] = _prompt; }/** LilypadCaller Interface Implementation */functionlilypadFulfilled(address_from,uint_jobId,LilypadResultType_resultType,stringcalldata_result) externaloverride {// Do something when the LilypadEvents contract returns // results successfully }functionlilypadCancelled(address_from,uint_jobId,stringcalldata_errorMsg) externaloverride {// Do something if there's an error returned by the// LilypadEvents contract }}