Closed manuelbarbas closed 5 months ago
SKALE Connect allows developers to access any external data source using the decentralized power of your SKALE Chain. If your dApp needs market data, weather temperatures, or Ethereum data, SKALE Connect provides a simple way to deliver this data to your Dapp.
How it works
oracle_submitRequest
GET or POST request to the SKALE chain containing the request specification.Request Formatting
To make a JSON-RPC request, send an HTTP POST request with a Content-Type: application/json
header. The JSON request data should contain 4 fields:
jsonrpc: <string>
, set to "2.0"
id: <number>
, a unique client-generated identifying integermethod: <string>
, a string containing the method to be invokedparams: <array>
, a JSON array of ordered parameter valuesThe response output will be a JSON object with the following fields:
jsonrpc: <string>
, matching the request specificationid: <number>
, matching the request identifierresult: <array|number|object|string>
, requested data or success confirmationRequests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.
Example
Below is shown how to make a request to the SKALE Connect. The full example, in Javascript, requests from the SKALE Calypso Testnet the balanceOf a NFT present on the Ethereum Goerli Network.
require("dotenv").config();
const {
chain
} = require("./config.json");
const { ethers } = require("ethers");
const deployment = require("./smart-contracts/deployments/deployments-4.json");
const claimAbi = require("./smart-contracts/artifacts/contracts/Claim.sol/Claim.json");
const {generateOracleRequest} = require("./oracle");
const PRIVATE_KEY = process.env.PRIVATE_KEY;
async function main() {
if (!PRIVATE_KEY) throw new Error("Private Key Not Found");
const provider = new ethers.JsonRpcProvider(chain.rpcUrl);
const signer = new ethers.Wallet(PRIVATE_KEY).connect(provider);
console.log(1);
const mainnetNFT = new ethers.Contract(deployment.mainnetNFT.address, deployment.mainnetNFT.abi);
const claim = new ethers.Contract(deployment.claim.address, claimAbi.abi, signer);
const request = {
"cid": 1,
"uri": "eth://",
"encoding": "json",
"ethApi": "eth_call",
"params": [{
"from": ethers.ZeroAddress,
"to": mainnetNFT.target,
"data": mainnetNFT.interface.encodeFunctionData(
"balanceOf",
[signer.address]
),
"gas": "0xfffff"
},
"latest"],
}
const requestStr = JSON.stringify(request);
const oracleResponse = await generateOracleRequest(requestStr.slice(1, requestStr.length - 1));
const res = oracleResponse["result"];
const paramsStartIndex = res.toString().search(/params/) + 8;
const paramsEndIndex = res.toString().search(/time/) - 2;
const parsedResult = JSON.parse(res);
const claimTransactionHash = await claim.claim([
parsedResult["cid"],
parsedResult["uri"],
parsedResult["encoding"],
parsedResult["ethApi"],
oracleResponse["result"].slice(paramsStartIndex, paramsEndIndex),
[],
[],
"",
parsedResult["time"],
parsedResult["rslts"],
parsedResult["sigs"].map((sig) => {
console.log(sig);
if (sig === null) {
return {
v: 0,
r: ethers.ZeroHash,
s: ethers.ZeroHash
}
} else {
let splitVals = sig.split(":");
return {
v: splitVals[0] == 0 ? 27 : 28,
r: ethers.zeroPadValue("0x" + (splitVals[1].length % 2 == 0 ? splitVals[1] : "0" + splitVals[1]), 32),
s: ethers.zeroPadValue("0x" + (splitVals[2].length % 2 == 0 ? splitVals[2] : "0" + splitVals[2]), 32)
}
}
})
], {
gasLimit: BigInt(140000000),
gasPrice: await provider.getFeeData().gasPrice
});
console.log("Claim Transaction Hash: ", claimTransactionHash);
const resultClaim = await claimTransactionHash.wait();
console.log("Result claim:", resultClaim);
}
main()
.catch((err) => {
process.exitCode = 1;
console.error(err);
})
Oracle Methods
Supported Geth Endpoints
Besides any http/https endpoint, the Oracle supports the following Geth JSON RPC endpoint for retrieving Ethereum network data:
eth://eth_call
JSON RPC API Reference
oracle_submitRequest (http(s))
Submits an Oracle request and returns a message receipt.
Parameters:
cid: <uint64>
- chain IDuri: <string>
- Oracle http|https endpoint. Must begin with http://
or https://
.time: <uint64>
- Linux time of request in msjsps: <array>
- list of JSON pointer to the data elements to be picked from the server response. The array must have 1 - 32 elements.encoding: <string>
- the only supported encoding is json
.trims: <uint64>
- an array of trim values used to trim endings of strings in the Oracle result. If trims
array is provided, it must provide trim values for each JSON pointer requested.post: <string>
- if provided, the Oracle will use POST instead of GET (default). The value will be posted to the endpoint.pow: <string>
- uint64 proof of work used to protect against DoS attacks.[NOTE]
uri
and jsps
parameter is 1024
bytes each.pow
must be the last parameter.Results:
The result will be an RpcResponse JSON object with result equal to:
<string>
- a message receipt used to check later if the result is readyExample:
[source]
// GET Request
{"cid": 1, "uri": "http://worldtimeapi.org/api/timezone/Europe/Kiev","jsps":["/unixtime", "/day_of_year", "/xxx"],"trims":[1,1,1],"time":9234567,"encoding":"json","pow":53458}
// POST Request
{"cid": 1, "uri": "https://reqres.in/api/users", "jsps":["/id"],"time":9234567,"post":"some data","encoding":"json","pow":1735}
oracle_submitRequest (eth)
Submits an Oracle request to an Ethereum API and returns a message receipt.
[NOTE]
Currently only eth_call
is supported.
Parameters:
cid: <uint64>
- chain IDuri: <string>
- Oracle geth endpoint. Must begin with eth://
.time: <uint64>
- Linux time of request in msethApi: <string>
- value of eth_call
.params: <string>
- params to eth_call
.encoding: <string>
- the only supported encoding is json
.pow: <string>
- uint64 proof of work used to protect against DoS attacks.[NOTE]
uri
is 1024
bytes each.params
is a json array of two elements: the first is an object consisting of from
, to
, data
, gas
. The second is a string blocknumber which can be latest
for a hex string. For example:"params":[{"to":"0x5FbDB2315678afecb367f032d93F642f64180aa3",
"from":"0x9876543210987654321098765432109876543210",
"data":"0x893d20e8", "gas":0x100000},"latest"]
pow
must be the last parameter.Example:
[source]
// eth_call Request
{"cid":1,"uri":"https://mygeth.com:1234","ethApi":"eth_call","params":[{"from":"0x9876543210987654321098765432109876543210","to":"0x5FbDB2315678afecb367f032d93F642f64180aa3","data":"0x893d20e8","gas":"0x100000"},"latest"],"encoding":"json","time":1681494451895,"pow":61535}
oracle_checkResult
Checks whether an Oracle result has been derived. By default the result is signed by stem:[t+1] nodes, where stem:[t] is the maximum number of untruthful nodes. Each node signs using its ETH wallet ECDSA key.
If no result has been derived, ORACLE_RESULT_NOT_READY
is returned.
Otherwise an error is returned.
The client is supposed to wait 1 second and try again.
Parameters:
receipt: <string>
- message receipt, returned by a call to oracle_submitRequest
Results:
The result repeats JSON elements from the corresponding Oracle request, plus includes a set of additional elements:
rslts: <array>
- string resultssigs : <array>
- ECDSA signatures where stem:[t] signatures are not null.Example:
[source]
// Response
{"cid":1, "uri":"http://worldtimeapi.org/api/timezone/Europe/Kiev","jsps":["/unixtime", "/day_of_year", "/xxx"],"trims":[1,1,1],"time":1642521456593, "encoding":"json","rslts":["164252145","1",null],"sigs":["6d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","7d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","8d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","9d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","1050daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","6d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f",null,null,null,null,null,null,null,null,null,null]}
// Response
{"cid":1,"uri":"https://mygeth.com:1234",,"ethApi":"eth_call","params":[{ "from":"0x9876543210987654321098765432109876543210","to":"0x5FbDB2315678afecb367f032d93F642f64180aa3","data":"0x893d20e8","gas":"0x100000"},"latest"],"encoding":"json","time":1681494451895, "rslts":["0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266"],"sigs"["6d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","7d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","8d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","9d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","1050daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f","6d50daf908d97d947fdcd387ed4bdc76149b11766f455b31c86d5734f4422c8f",null,null,null,null,null,null,null,null,null,null]}
ORACLE_SUCCESS 0
ORACLE_UNKNOWN_RECEIPT 1
ORACLE_TIMEOUT 2
ORACLE_NO_CONSENSUS 3
ORACLE_UNKNOWN_ERROR 4
ORACLE_RESULT_NOT_READY 5
ORACLE_DUPLICATE_REQUEST 6
ORACLE_COULD_NOT_CONNECT_TO_ENDPOINT 7
ORACLE_ENDPOINT_JSON_RESPONSE_COULD_NOT_BE_PARSED 8
ORACLE_INTERNAL_SERVER_ERROR 9
ORACLE_INVALID_JSON_REQUEST 10
ORACLE_TIME_IN_REQUEST_SPEC_TOO_OLD 11
ORACLE_TIME_IN_REQUEST_SPEC_IN_THE_FUTURE 11
ORACLE_INVALID_CHAIN_ID 12
ORACLE_REQUEST_TOO_LARGE 13
ORACLE_RESULT_TOO_LARGE 14
ORACLE_ETH_METHOD_NOT_SUPPORTED 15
ORACLE_URI_TOO_SHORT 16
ORACLE_URI_TOO_LONG 17
ORACLE_UNKNOWN_ENCODING 18
ORACLE_INVALID_URI_START 19
ORACLE_INVALID_URI 20
ORACLE_USERNAME_IN_URI 21
ORACLE_PASSWORD_IN_URI 22
ORACLE_IP_ADDRESS_IN_URI 23
ORACLE_UNPARSABLE_SPEC 24
ORACLE_NO_CHAIN_ID_IN_SPEC 25
ORACLE_NON_UINT64_CHAIN_ID_IN_SPEC 26
ORACLE_NO_URI_IN_SPEC 27
ORACLE_NON_STRING_URI_IN_SPEC 28
ORACLE_NO_ENCODING_IN_SPEC 29
ORACLE_NON_STRING_ENCODING_IN_SPEC 30
ORACLE_TIME_IN_SPEC_NO_UINT64 31
ORACLE_POW_IN_SPEC_NO_UINT64 32
ORACLE_POW_DID_NOT_VERIFY 33
ORACLE_ETH_API_NOT_STRING 34
ORACLE_ETH_API_NOT_PROVIDED 35
ORACLE_JSPS_NOT_PROVIDED 36
ORACLE_JSPS_NOT_ARRAY 37
ORACLE_JSPS_EMPTY 38
ORACLE_TOO_MANY_JSPS 39
ORACLE_JSP_TOO_LONG 40
ORACLE_JSP_NOT_STRING 41
ORACLE_TRIMS_ITEM_NOT_STRING 42
ORACLE_JSPS_TRIMS_SIZE_NOT_EQUAL 43
ORACLE_POST_NOT_STRING 44
ORACLE_POST_STRING_TOO_LARGE 45
ORACLE_NO_PARAMS_ETH_CALL 46
ORACLE_PARAMS_ARRAY_INCORRECT_SIZE 47
ORACLE_PARAMS_ARRAY_FIRST_ELEMENT_NOT_OBJECT 48
ORACLE_PARAMS_INVALID_FROM_ADDRESS 49
ORACLE_PARAMS_INVALID_TO_ADDRESS 50
ORACLE_PARAMS_ARRAY_INCORRECT_COUNT 51
ORACLE_BLOCK_NUMBER_NOT_STRING 52
ORACLE_INVALID_BLOCK_NUMBER 53
ORACLE_MISSING_FIELD 54
ORACLE_INVALID_FIELD 55
ORACLE_EMPTY_JSON_RESPONSE 56
ORACLE_COULD_NOT_PROCESS_JSPS_IN_JSON_RESPONSE 57
ORACLE_NO_TIME_IN_SPEC 58
ORACLE_NO_POW_IN_SPEC 59
ORACLE_HSPS_TRIMS_SIZE_NOT_EQUAL 60
ORACLE_PARAMS_NO_ARRAY 61
ORACLE_PARAMS_GAS_NOT_UINT64 62
Suggestion for this Section
Divide example and theory since there's a lot of theory
Also, since we want to make sure the user on-boarding it's as simple as possible maybe we could have an Example Implementation section explaining step by step/script by script