@pugshole/lilac-contracts
v0.1.0
Published
Reference Solidity contracts for integrating with the Lilac verifiable AI oracle (Base mainnet and supported chains).
Downloads
26
Maintainers
Readme
@pugshole/lilac-contracts
Reference Solidity contracts for integrating with the Lilac verifiable AI oracle.
Live on Base mainnet (chainId 8453) and other supported chains — see
https://docs.base-oracle.cloud for the canonical
list of Oracle addresses per chain.
Install
# Foundry
forge install maxshtun999/lilac-oracle
# Hardhat / npm
npm install --save-dev @pugshole/lilac-contractsIf you used forge install, add this remapping to your foundry.toml:
remappings = [
"@pugshole/lilac-contracts/=lib/contracts/src/",
]Quickstart
Inherit LilacConsumer and override _onLilacAnswer:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {LilacConsumer} from "@pugshole/lilac-contracts/LilacConsumer.sol";
import {IOracle} from "@pugshole/lilac-contracts/IOracle.sol";
contract MyMarket is LilacConsumer {
event Settled(bytes32 indexed requestId, bool yes, uint8 confidence);
/// @param oracle The Lilac Oracle address for the chain you're deploying to.
/// @dev Base mainnet: 0x... (see docs)
/// Arbitrum: 0x... (see docs)
constructor(address oracle) LilacConsumer(oracle) {}
/// @notice User-facing entrypoint. Pass in the quote your backend obtained from
/// Lilac's REST `/quote` endpoint, signed for `requester = address(this)`.
function settle(string calldata question, IOracle.QuoteSig calldata quote)
external
payable
returns (bytes32 requestId)
{
string[] memory options = new string[](2);
options[0] = "YES";
options[1] = "NO";
requestId = _requestData(question, options, "", quote);
}
/// @notice Called by the Lilac Oracle once the node has signed a result.
function _onLilacAnswer(bytes32 requestId, bytes calldata answer, uint8 confidence)
internal
override
{
// Decode according to your `rules` schema. Trivial example: first byte = YES/NO.
bool yes = answer.length > 0 && answer[0] != 0x00;
emit Settled(requestId, yes, confidence);
}
}API surface
LilacConsumer (abstract):
| Member | Visibility | Purpose |
|--------------------------------------------------|------------|-------------------------------------------------------------------|
| constructor(address oracle_) | public | Pin the trusted Oracle address (immutable). |
| LILAC_ORACLE | immutable | The configured Oracle. IOracle-typed. |
| _requestData(question, options, rules, quote) | internal | Forward msg.value to oracle.requestData. Returns requestId. |
| _claimRefund(requestId) | internal | Call oracle.claimRefund after the timeout has elapsed. |
| _onLilacAnswer(requestId, answer, confidence) | internal | Override this. Fires only on Oracle-verified answers. |
| oracleCallback(requestId, answer, confidence) | external | Entrypoint the Oracle calls. Gated on msg.sender == oracle. |
Quote signing
LilacConsumer._requestData makes the inheriting contract the requester from the
Oracle's point of view. That means your backend must obtain a quote signed for:
requester = address(yourConsumerContract)nonce = oracle.nonces(address(yourConsumerContract))
…not for tx.origin or the user's EOA. The Lilac REST /quote endpoint accepts a
requester parameter for exactly this case — pass your deployed consumer's address.
Refunds
If the Oracle times out (default 30 minutes), call _claimRefund(requestId) to recover
the escrowed ETH. The Oracle refunds the requester — which is your consumer contract,
not the original user. Build your own ledger / receive() if you need to forward refunds.
Files
src/LilacConsumer.sol— abstract base contract (inherit this).src/ILilacConsumer.sol— callback interface (implement this if you don't inherit).src/IOracle.sol— Oracle interface (types:Tier,Status,QuoteSig,Request,Result).src/IFeeManager.sol— FeeManager interface (getFee(tier)is the useful part).
Security
The Oracle ECDSA-verifies the node's EIP-712 signature against the node bound at request
time before invoking oracleCallback. LilacConsumer.oracleCallback additionally gates on
msg.sender == LILAC_ORACLE. If you implement ILilacConsumer directly, you must add
the same msg.sender check yourself — otherwise anyone can spoof a callback.
The _onLilacAnswer hook is invoked with the Oracle's callbackGasLimit (default 200k).
Keep your hook cheap; revert-on-overflow is fine — the Oracle catches it, distributes the
node fee, and emits CallbackFailed. Your funds are not at risk on revert, but your answer
is lost.
License
MIT
