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
To trigger a job from your contract, you need to call the LilypadEvents contract which the bridge is listening to. You will connect to Bacalhau network via this bridge. Create an instance of LilypadEvents in your own contract by passing the public contract address above to the LilypadEvents constructor. See Deployed Network Detailsfor address details
To make a call to Bacalhau, call runLilypadJob from your function. You need to pass the following parameters:
Name
Type
Purpose
_from
address
The address of the calling contract, to which success or failure will be passed back. You should probably use address(this) from your contract.
_spec
string
A Bacalhau job spec in JSON format. See below for more information on creating a job spec.
The type of result that you want to be returned. If you specify CID, the result tree will come back as a retrievable IPFS CID. If you specify StdOut, StdErr or ExitCode, those raw values output from the job will be returned.
LilypadEvents Contract
This contract is the bridge between the Bacalhau network and smart contracts & does all the heavy lifting.
Note - you cannot deploy your own version of this contract. It will not trigger Bacalhau jobs to run on its own and requires the Lilypad GO Daemon integration. You need to use the address of the deployed LilypadEvents contract within your own smart contract.
To use Lilypad, you only need to take note of one function in this events contract - the runLilypadJob(address _from, string memory _spec, uint8 _resultType) function which takes the following parameters.\
Name
Type
Purpose
_from
address
The address of the calling contract, to which success or failure will be passed back. You should probably use address(this) from your contract.
_spec
string
A Bacalhau job spec in JSON format. See docs for more information on creating a job spec.
The type of result that you want to be returned - specified in the LilypadCaller Interface. If you specify CID, the result tree will come back as a retrievable IPFS CID. If you specify StdOut, StdErr or ExitCode, those raw values output from the job will be returned.
Open LilypadEvents Contract in Remix -> Click here
// SPDX-License-Identifier: MITpragmasolidity >=0.8.4;import"hardhat/console.sol";import"@openzeppelin/contracts/utils/Counters.sol";import"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";import"@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";import"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";import"./LilypadCallerInterface.sol";errorLilypadEventsUpgradeableError();/**@notice An experimental contract for POC work to call Bacalhau jobs from FVM smart contracts*/contractLilypadEventsUpgradeableisInitializable, AccessControlUpgradeable, UUPSUpgradeable {boolprivate initialized;bytes32publicconstant UPGRADER_ROLE =keccak256("UPGRADER_ROLE");uint256public LILYPAD_FEE =0.03*10**18;uint256private escrowAmount =0;uint256private escrowMinAutoPay =5*10**18;addressprivate escrowAddress =0x5617493b265E9d3CC65CE55eAB7798796D9108E4; uint256[50] private __gap; //for extra memory slots that may be needed in future upgrades.usingCountersforCounters.Counter; Counters.Counter private _jobIds;/// @custom:oz-upgrades-unsafe-allow constructorconstructor() {_disableInitializers(); }functioninitialize() publicinitializer {require(!initialized,"Contract Instance has already been initialized"); console.log("Deploying LilypadEvents contract"); initialized =true;__AccessControl_init();__UUPSUpgradeable_init();_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);_grantRole(UPGRADER_ROLE, msg.sender); }function_authorizeUpgrade(address newImplementation)internalonlyRole(UPGRADER_ROLE)override {}structLilypadJob {address requestor;uint id; //jobIDstring spec; LilypadResultType resultType; } LilypadJob[] public lilypadJobHistory;structLilypadJobResult {address requestor;uint id;bool success;string result; LilypadResultType resultType; } LilypadJobResult[] public lilypadJobResultHistory;mapping(address=> LilypadJobResult[]) lilypadJobResultByAddress; // jobs by requestor/** Events **/eventNewLilypadJobSubmitted(LilypadJob job);eventLilypadJobResultsReturned(LilypadJobResult result);eventLilypadEscrowPaid(address,uint256);/** Escrow/ Balance functions **/functiongetEscrowAddress()publicviewonlyRole(UPGRADER_ROLE) returns(address) {return escrowAddress; }functiongetEsrowMinAutoPay()publicviewonlyRole(UPGRADER_ROLE) returns(uint256) {return escrowMinAutoPay; }functionsetEscrowMinAutoPay(uint256_minAmount) publiconlyRole(UPGRADER_ROLE){ escrowMinAutoPay = _minAmount; }functionsetEscrowWalletAddress(address_escrowAddress) publiconlyRole(UPGRADER_ROLE) { escrowAddress = _escrowAddress; }functionwithdrawBalanceToEscrowAddress() publiconlyRole(UPGRADER_ROLE) {require(address(this).balance >0,"No money in contract able to be withdrawn");addresspayable recipient =payable(escrowAddress);//ecrowAmount and contract amount Should be the same (if not should be zero'd anyway); escrowAmount =0;uint256 amount =address(this).balance; recipient.transfer(amount);emitLilypadEscrowPaid(recipient, amount); }functiongetLilypadFee() publicviewreturns (uint256) {return LILYPAD_FEE; }functiongetContractBalance() publicviewonlyRole(UPGRADER_ROLE) returns (uint256) {returnaddress(this).balance; }functionsetLilypadFee(uint256_newFee) publiconlyRole(UPGRADER_ROLE) { LILYPAD_FEE=_newFee; }/** Lilypad Job via Bacalhau network functions **//** Run your own docker image spec on the network with the _specName = "CustomSpec", You need to pass in the Bacalhau specification for this **/functionrunLilypadJob(address_from,stringmemory_spec,uint8_resultType) publicpayablereturns (uint) {require(msg.value >= LILYPAD_FEE,"Not enough payment sent to cover job fee");uint thisJobId = _jobIds.current(); LilypadJob memory jobCalled =LilypadJob({ requestor: _from, id: thisJobId, spec: _spec, resultType:LilypadResultType(_resultType) }); lilypadJobHistory.push(jobCalled);emitNewLilypadJobSubmitted(jobCalled); _jobIds.increment(); escrowAmount += msg.value; if(escrowAmount > escrowMinAutoPay){uint256 escrowToSend = escrowAmount;addresspayable recipient =payable(escrowAddress);//should check contract balance before proceedingif(address(this).balance >= escrowToSend) { escrowAmount =0; recipient.transfer(escrowToSend);emitLilypadEscrowPaid(recipient, escrowToSend); } }return thisJobId; }// this should really be owner only - our admin contract should be the only one able to call itfunctionreturnLilypadResults(address_to,uint_jobId,LilypadResultType_resultType,stringmemory_result) public { LilypadJobResult memory jobResult =LilypadJobResult({ requestor: _to, id: _jobId, result: _result, success:true, resultType: _resultType }); lilypadJobResultHistory.push(jobResult); lilypadJobResultByAddress[_to].push(jobResult);emitLilypadJobResultsReturned(jobResult);LilypadCallerInterface(_to).lilypadFulfilled(address(this), _jobId, _resultType, _result); }functionreturnLilypadError(address_to,uint_jobId,stringmemory_errorMsg) publiconlyRole(UPGRADER_ROLE) { LilypadJobResult memory jobResult =LilypadJobResult({ requestor: _to, id: _jobId, success:false, result: _errorMsg, resultType: LilypadResultType.StdErr }); lilypadJobResultHistory.push(jobResult); lilypadJobResultByAddress[_to].push(jobResult);emitLilypadJobResultsReturned(jobResult);LilypadCallerInterface(_to).lilypadCancelled(address(this), _jobId, _errorMsg); }functionfetchAllJobs() publicviewreturns (LilypadJob[] memory) {return lilypadJobHistory; }functionfetchAllResults() publicviewreturns (LilypadJobResult[] memory) {return lilypadJobResultHistory; }functionfetchJobsByAddress(address_requestor) publicviewreturns (LilypadJobResult[] memory) {return lilypadJobResultByAddress[_requestor]; }}
LilypadCaller Interface
This interface ensures that the results of a job run on the Bacalhau network via Lilypad can be returned to the originating contract.
This interface needs to be implemented by a smart contract for Lilypad to run.
Open the LilypadCaller Interface in Remix -> Click here