skalenetwork / docs.skale.space

https://docs.skale.space/
MIT License
1 stars 2 forks source link

SKALE Connect #22

Closed manuelbarbas closed 5 months ago

manuelbarbas commented 11 months ago

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

  1. Claim.sol
  2. ClaimableToken.sol
  3. MainnetNFT.sol
  4. Oracle.js
  5. Oracle-utils.js
  6. Idex.js
manuelbarbas commented 11 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.

Hands On

How it works

  1. A client submits a JSON RPC oracle_submitRequest GET or POST request to the SKALE chain containing the request specification.
  2. The SKALE daemon (skaled) distributes the request to all other nodes in the SKALE Chain and the client is presented with a receipt.
  3. Each of the 16 SKALE nodes performs the request, retrieves the data, and signs the result with it's ECDSA key.
  4. The Oracle result is returned when $t+1$ nodes sign the same result, where stem:[t] is the maximum number of untruthful nodes. On SKALE Chains $t$ is $[n\left(\frac{1}{3} \right)]$.

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:

The response output will be a JSON object with the following fields:

Requests 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);
    })

Theory

Oracle Methods

Supported Geth Endpoints

Besides any http/https endpoint, the Oracle supports the following Geth JSON RPC endpoint for retrieving Ethereum network data:

JSON RPC API Reference

oracle_submitRequest (http(s))

Submits an Oracle request and returns a message receipt.

Parameters:

[NOTE]

Results:

The result will be an RpcResponse JSON object with result equal to:

Example:

[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:

[NOTE]

"params":[{"to":"0x5FbDB2315678afecb367f032d93F642f64180aa3",
"from":"0x9876543210987654321098765432109876543210",
"data":"0x893d20e8", "gas":0x100000},"latest"]

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:

Results:

The result repeats JSON elements from the corresponding Oracle request, plus includes a set of additional elements:

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