Making Arbitrary TLS Calls (TLS Oracle)
Clique supports clients to make arbitrary TLS calls from smart contracts in a verifiable format. For generic TLS calls, we have provided the generic clique_httpsRequest
Built-In task. Users can create queries for the clique_httpsRequest
task to obtain the results of TLS calls. Users can also create more complex tasks of Schema
type to utilize the clique_httpsRequest
task.
clique_httpsRequest
Here is the manifest file for the clique_httpsRequest
task:
spec-version = "1"
name = "clique_httpsRequest"
type = "BuiltIn"
proof-type = ["TEE"]
[types.Transformation]
from = { type = "string", description = "json pointer to field in response" }
soltype = { type = "string", description = "solidity type" }
[input]
url = { type = "string", description = "Request url" }
encoding = { type = "Transformation[]", description = "encoding format" }
[output]
response = { type = "bytes", description = "ABI encoded" }
clique_httpsRequest
is a built-in task, which means users can directly use this task within the Clique Network without the need to publish it additionally.
This task has two output parameters:
url
: The URL to be accessed for this TLS call. The URL needs to return parseable results in JSON format.encoding
: Indicates how to extract data from the returned JSON result. Here we use an array of custom types (Transformation
) as the input parameter. TheTransformation
custom type contains two fields:from
is a JSON pointer for extracting data from JSON, andsoltype
is the Solidity type into which the extracted data should be encoded.
This task has only one return value: response
. The response
is data encoded in Solidity ABI format, with the encoding format specified by the input parameter encoding
. After obtaining the result of the TLS call, the smart contract can decode the data using ABI decoding to retrieve the information.
Make TLS Calls from Smart Contracts
Here is a smart contract example, initiating a TLS call to https://jsonplaceholder.typicode.com/users and extracting data.
This URL will return JSON data in the following format:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "[email protected]",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
...
]
In the following example, we will use TLS Call to extract id
, name
, lat
, and the entire JSON array from the returned JSON result.
pragma solidity ^0.8.22;
import {ICliqueTaskManager} from "../src/ICliqueTaskManager.sol";
struct Transformation {
string from;
string soltype;
}
struct Params {
string url;
Transformation[] encoding;
}
struct Response {
bytes response;
}
struct Result {
uint32 id;
string name;
string geoLat;
string[] array;
}
contract HttpsRequest {
address public immutable _manager;
event CallbackInvoked(Result);
constructor(address manager) {
_manager = manager;
}
function callback(bytes calldata _response) external {
Response memory response = abi.decode(_response, (Response));
Result memory result = abi.decode(response.response, (Result));
emit CallbackInvoked(result);
}
function run(uint32 id) public {
Transformation[] memory transformations = new Transformation[](4);
transformations[0] = Transformation({from: "/0/id", soltype: "uint32"});
transformations[1] = Transformation({from: "/0/name", soltype: "string"});
transformations[2] = Transformation({from: "/0/address/geo/lat", soltype: "string"});
transformations[3] = Transformation({from: "", soltype: "string[]"});
Params memory params = Params({
url: "https://jsonplaceholder.typicode.com/users",
encoding: transformations
});
ICliqueTaskManager.Task memory task = ICliqueTaskManager.Task(
id,
"clique_httpsRequest",
abi.encode(params)
);
ICliqueTaskManager(_manager).createNewTask{value: 0.05 ether}(
abi.encode(task),
this.callback.selector
);
}
}
Let's break down the example.
import {ICliqueTaskManager} from "../src/ICliqueTaskManager.sol";
Import the Clique Contract SDK here.
struct Transformation {
string from;
string soltype;
}
Define the custom typeTransformation
for the clique_httpsRequest
task. The name of the structure is not important, but the content of the structure must be consistent with the custom type inclique_httpsRequest
task.
The names of the other structures to be defined next are not important; users can define names as they wish.
struct Params {
string url;
Transformation[] encoding;
}
Define the input parameters according to the input type of the clique_httpsRequest
task.
struct Response {
bytes response;
}
Define the output parameters according to the output type of the clique_httpsRequest
task.
struct Result {
uint32 id;
string name;
string geoLat;
string[] array;
}
Define the decoded result of the TLS call. The clique_httpsRequest
task will return the encoded result, which will be decoded according to this structure.
Transformation[] memory transformations = new Transformation[](4);
transformations[0] = Transformation({from: "/0/id", soltype: "uint32"});
transformations[1] = Transformation({from: "/0/name", soltype: "string"});
transformations[2] = Transformation({from: "/0/address/geo/lat", soltype: "string"});
transformations[3] = Transformation({from: "", soltype: "string[]"});
Create the Transformation
structure needed in the input parameters, using JSON Pointer to specify how to extract data from the JSON result, and determine how to encode the extracted data. The result data will be encoded in the order specified by the Transformation
array.
Params memory params = Params({
url: "https://jsonplaceholder.typicode.com/users",
encoding: transformations
});
Construct the input parameters.
ICliqueTaskManager.Task memory task = ICliqueTaskManager.Task(
id,
"clique_httpsRequest",
abi.encode(params)
);
Construct the clique_httpsRequest
query.
ICliqueTaskManager(_manager).createNewTask{value: 0.05 ether}(
abi.encode(task),
this.callback.selector
);
Use the Clique Contract SDK to create a query and wait for the callback function to execute.
function callback(bytes calldata _response) external {
Response memory response = abi.decode(_response, (Response));
Result memory result = abi.decode(response.response, (Result));
emit CallbackInvoked(result);
}
Define the callback function and decode the result of the TLS call within the callback function.
Make TLS Calls from Client SDK
Although the primary use case for TLS Calls is in smart contracts, it is also supported off-chain. Users can use the Clique Client SDK to call the clique_httpsRequest
task.
For example, use our Rust SDK:
Add this dependency to your Cargo.toml
clique-client-sdk = { git = "https://github.com/CliqueOfficial/clique-protocol-sdk" }
serde_json = { version = "1.0", features = ["preserve_order"] }
tokio = { version = "1.38.0", features = ["full"] }
use clique_client_sdk::CliqueClient;
use serde_json::json;
#[tokio::main]
async fn main() {
let endpoint = "https://localhost:8000";
let client = CliqueClient::new(endpoint).unwrap();
let id = 1;
let url = "https://jsonplaceholder.typicode.com/users";
let encoding = json!([
{"from": "/0/id", "soltype": "int16"},
{"from": "/0/name", "soltype": "string"},
{"from": "/0/address/geo/lat", "soltype": "string"}
]);
let json_query = json!({
"id": id,
"method": "clique_httpsRequest",
"params": {"url": url, "encoding": encoding },
"input_types": {"url": "string", "encoding": "Transformation[]"},
"custom_types": {"Transformation": {"from": "string", "soltype": "string"}}
});
let result = client.run_query(json_query).await.unwrap();
}
Make TLS Calls through Schema Task
In addition to directly using the clique_httpsRequest
task, users can also customize their own Schema
tasks and indirectly use clique_httpsRequest
within these Schema
tasks to construct more complex queries.
Here is a simple example of a Schema
task's manifest file. After defining the manifest file, users need to first publish it to the Clique Network using Clique CLI, and then create the corresponding query to use it.
specVersion = "1"
name = "yourOrganization_customRequest"
type = "Schema"
proof-type = ["TEE"]
[types.Transformation]
from = { type = "string", description = "json pointer to field in response" }
soltype = { type = "string", description = "solidity type" }
[input]
url = { type = "string", description = "Request url" }
encoding = { type = "Transformation[]", description = "encoding format" }
[output]
response = { ref = "$tasks.clique_httpsRequest.response" }
[[tasks]]
name = "clique_httpsRequest"
proof-preference = "TEE"
[tasks.input]
url = "$input.url"
encoding = "$input.encoding"
Last updated