发布到智能合约(Chainlink 直接请求)

使用 Chainlink 直接请求在链上发布 SxT 查询结果。

🚧

如果您是《空间和时间》的新手,我们建议您首先按照入门指南熟悉基本概念。

Chainlink 直接请求(DR)允许智能合约以安全和去中心化的方式访问外部数据和 API。智能合约要求特定的 Chainlink 节点从指定的来源检索特定的数据。然后,节点获取数据并将其返回给合约,然后合约可以使用它来执行其编程逻辑。

📘安全注意事项

DR 可用于访问各种数据源。数据源的安全性会影响DR的整体安全性,即数据源安全可靠,Chainlink节点返回的数据也将是安全的。如果数据源容易受到篡改或其他安全风险,则可能会危及灾难恢复的安全性。

Space and Time 通过以加密方式保证 SxT 数据仓库中运行的查询结果,提供安全可靠的数据源。这为智能合约开发人员通过以完全安全和防篡改的方式集成对链上和链下数据的分析来编程复杂的业务逻辑提供了丰富的新机会。

使用直接请求发布 SxT 查询的概述

post image
  1. 用户在区块链上部署智能合约,其中包含定义其需要访问的数据和使用条款的代码 (SQL)。用户使用 和 查询参数向 SxTRelay 合约发出 API 请求,并支付发送请求的最低金额。requestId

  2. SxTRelay合约将用户的地址作为唯一标识符。它使用现有的 Chainlink 客户端请求合约,并具有 等方法,向 Chainlink 算子发送请求,并将查询结果写回用户的智能合约。requestQuerysaveQuery

  3. SxTRelay 合约使用用户合约地址、和 URL发出此请求的事件。requestId

  4. SxTRelay 合约通过 Chainlink Operator 合约向特定 Chainlink 节点请求数据,指定数据源和请求参数。

  5. Chainlink 节点通过监听发出的事件来接收请求,并从指定的源获取数据。它从 SxT 编写的外部适配器请求数据。该适配器直接对 SxT 验证器层进行 API 调用,以从 SxT 数据仓库集群检索查询结果。

  6. SxT 验证器层将查询请求发送到存储整个区块链历史记录的 SxT 数据仓库集群。

  7. 外部适配器检索信息,Chainlink 作业任务之一解析 JSON 查询结果并将数据编码在 2D 数组中。

  8. Chainlink 节点通过交易将数据返回给 Chainlink Operator 智能合约。

  9. Chainlink Operator 通过 SxTRelay 合约将数据发送到 User 智能合约,并带有回调函数和合约地址。SxTRelay 合约将请求保存回用户合约,然后用户合约可以使用它来执行其编程逻辑。本质上,Chainlink 操作员节点生成报告并进行 Web3 调用以将该报告发送到智能合约。

  10. 智能合约处理数据并可能执行其他操作,例如更新其内部状态或触发其他智能合约。

合约

用户请求演示

示例用户合约继承了 UserRequest 抽象合约,用于发送请求并记录其已完成的预言机请求。

  • 本合约由每个项目/智能合约所有者部署。

  • 它应该继承UserRequest:

JSX

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./SxTRequest.sol";
import "./interfaces/ChainlinkTokenInterface.sol";

/**
 * @title User Request contract
 */
abstract contract UserRequest {

    // Zero Address
    address constant ZERO_ADDRESS = address(0);

    /// @dev SxT Request contract address
    address public sxtRequestContract;

    /// @dev Chainlink token address
    ChainlinkTokenInterface public chainlinkToken;

    /// @dev Current request Id
    bytes32 public currentRequestId;

    /** 
     * @dev The constructor sets the SxTRequest and validator contract address
     * @param sxtRequestAddress - Address of the SxT request contract address that has Oracle and Job initialized on it
     * @param chainlinkTokenAddress - Address of the LINK token that would be used for payment
     */
    constructor (address sxtRequestAddress, ChainlinkTokenInterface chainlinkTokenAddress) {
        require(sxtRequestAddress != ZERO_ADDRESS, "UserRequest: Cannot set to Zero Address");
        require(chainlinkTokenAddress != ChainlinkTokenInterface(ZERO_ADDRESS), "UserRequest: Cannot set to Zero Address");
        sxtRequestContract = sxtRequestAddress;
        chainlinkToken = chainlinkTokenAddress;
    }

    /**
     * @dev Modifier to constraint only the SxTRequest contract to call the function
     */
    modifier onlySxTRequest() {
        require(msg.sender == sxtRequestContract, "Only callable by SxT Request Contract");
        _;
    }

    /**
     * @dev triggers the requestQuery function of the SxTRequest contract
     * @param resourceId - request id
     * @param query - user query
     */
    function runSxTRequest(string memory query, string memory resourceId) external returns(bytes32 requestId){
        SxTRequest sxtRequestInstance = SxTRequest(sxtRequestContract);
        require(chainlinkToken.approve(sxtRequestContract, sxtRequestInstance.FEE()), "SxTRequest: insufficient allowance");
        return bytes32(abi.encodePacked(sxtRequestInstance.requestQuery(address(this), query, resourceId)));
    }

    /**
     * @dev The node calls this function to write the result of the query
     * @param requestId - request id
     * @param data - response of the user query
     */
    function saveQueryResponse(bytes32 requestId, string[][] calldata data) external virtual onlySxTRequest {}

    /**
     * @dev Withdraw Chainlink from contract
     * @param to - Address to transfer the LINK tokens
     * @param amount - Amount of the LINK tokens to transfer
     */
    function withdrawChainlink(address to, uint256 amount) external {
        bool transferResult = chainlinkToken.transfer(
            to,
            amount
        );
        require(transferResult, "SxTRequest: Chainlink token transfer failed");
    }
}
  • 该请求将被发送到 SxTRelay 合约,并且该合约将收到响应。

智能合约功能

构造函数(地址sxtRequestAddress, ChainlinkTokenInterface chainlinkTokenAddress

  • sxtRequestAddress- 初始化了Oracle和Job的SxT请求合约地址

  • chainlinkTokenAddress- 用于支付的 LINK 代币的地址

  • 该函数可以由合约所有者调用。

runSxTRequest(字符串内存查询,字符串内存资源Id)外部返回(bytes32 requestId)

  • query- SxT 请求的 SQL 查询

  • resourceId- SQL 查询用于从验证器层路由数据的名字namespace.table

通过合约执行SQL查询SxTRelay

  • resourceId- 用于识别验证器层中的数据仓库集群。通常是 SQL 查询中使用的第一个表名

  • sqlText是将在 SxT 上执行的 SQL 查询

  • 该函数可以由合约所有者调用。

saveResponse(bytes32 requestId, string[][] 响应)

  • 由SxTRelay执行,从Chainlink节点算子返回数据

  • 更新当前响应数据

  • requestId必须等于currentRequestId

  • 该函数只能由Chainlink节点操作者调用。

🚧

文档的其余部分基于在 SxT Alpha-DEV 集群和 Goerli 网络之上运行。以太坊主网尚不支持。

用户示例部署

  1. 部署 UserRequestDemo 合约。(请参阅下图由 SxT 部署的 Chainlink Operator 和 SxTRequest 合约地址)。

    用户合同样本:

JSX

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./UserRequest.sol";
import "./ConfirmedOwner.sol";

/**
 * @title User Request contract
 */
contract UserRequestDemo is UserRequest, ConfirmedOwner {

    /// @dev Chainlink Request call response
    string[][] public currentResponse;

    /** 
     * @dev The constructor sets the SxTRequest and validator contract address
     * @param sxtRequestAddress - SxT request contract address
     * @param chainlinkTokenAddress - Chainlink Token address
     */
    constructor (address sxtRequestAddress, ChainlinkTokenInterface chainlinkTokenAddress)
        UserRequest(sxtRequestAddress, chainlinkTokenAddress)
        ConfirmedOwner(msg.sender)
    {}

    /**
     * @dev The node calls this function to write the result of the query
     * @dev The SxT request contract will be looking for the function name saveQueryResponse for saving the response
     * @param requestId - request id
     * @param data - response of the user query
     */
    function saveQueryResponse(bytes32 requestId, string[][] calldata data) external override {
        delete currentResponse;
        currentRequestId = requestId;
        // Store response
        for (uint256 i = 0; i < data.length; i++) {
            uint256 inLength = data[i].length;
            string[] memory row = new stringUnsupported embed;
            for (uint256 j = 0; j < inLength; j++) {
                row[j] = data[i][j];
            }
            currentResponse.push(row);
        }
    }
}
$ npx hardhat deploy:UserRequestDemo --network goerli
$ npx hardhat verify:UserRequestDemo --network goerli
  1. 将 LINK 代币转移到 UserRequestDemo 合约以支付所需的 SQL 查询。使用为此目的提供的水龙头链接。

Faucet Link: https://goerlifaucet.com/, https://goerli-faucet.pk910.de/
  1. 调用 UserRequestDemo 合约函数从 SxT 数据库检索响应。将所需的查询和资源 ID 作为参数传递。

$ npx hardhat action:UserRequestDemo:runSxTRequest --resourceid "ETH.WALLET"
  1. 检查 UserRequestDemo 响应函数 currentResponse 中的响应。传递所需的数组索引以检索所需的响应。

$ npx hardhat action:UserRequestDemo:currentResponse --inputrow 0 --inputcolumn 0

目前在孟买部署的合同:

姓名地址SxTChainlink操作符:0xecd41Fd32C8E25dc2ca86D4528d966bA0Cdf58b8SxTPublishDataFactory:0xf68a54ff4580d2B12E5fC45c31Fa4aea3e981e78SxTRelayProxy:0xf6b18242dab7af6F7390505fCFd16e03F61F8bCB用户请求字节:0x57fBbCBABfa409D6C5E75387080c9a07aa714f5c用户请求字符串:0x00962Fc5168f83278F1dD32543C335b53a5393D5用户请求字符串2D:0xbc4Cb22ce56fA8869096Af9E3E87a94d9396440E用户请求Uint256:0xaCBB7771d778aAd35f5b46E26E2639cBc45Dd27F

当前 JobId

询问

数据类型ID字节:bc97c680d2924f31a0581d947314cc62细绳:bc97c680d2924f31a0581d947314cc63字符串二维:bc97c680d2924f31a0581d947314cc64单位256:bc97c680d2924f31a0581d947314cc61

看法

数据类型ID字节:bc97c680d2924f31a0581d947314cc72细绳:bc97c680d2924f31a0581d947314cc73字符串二维:bc97c680d2924f31a0581d947314cc84单位256:bc97c680d2924f31a0581d947314cc71

https://www.notion.so/d328d59de18a4306bfe90c7303cadb6f

环境合同地址格尔利Chainlink运营商合约0x1EF964d2680fF5d346c8aE8D5A8dfcF7DFFAC202格尔利SXT中继0x07196eac2f0F60499D924d998E79457E4e7714b1


结束

Chainlink算子

JSX

[{"inputs":[{"internalType":"address","name":"_chainlink","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"senders","type":"address[]"},{"indexed":false,"internalType":"address","name":"changedBy","type":"address"}],"name":"AuthorizedSendersChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"}],"name":"CancelOracleRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"specId","type":"bytes32"},{"indexed":false,"internalType":"address","name":"requester","type":"address"},{"indexed":false,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"},{"indexed":false,"internalType":"address","name":"callbackAddr","type":"address"},{"indexed":false,"internalType":"bytes4","name":"callbackFunctionId","type":"bytes4"},{"indexed":false,"internalType":"uint256","name":"cancelExpiration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dataVersion","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"OracleRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"}],"name":"OracleResponse","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"acceptedContract","type":"address"}],"name":"OwnableContractAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"targets","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"senders","type":"address[]"},{"indexed":false,"internalType":"address","name":"changedBy","type":"address"}],"name":"TargetsUpdatedAuthorizedSenders","type":"event"},{"inputs":[{"internalType":"address[]","name":"targets","type":"address[]"},{"internalType":"address[]","name":"senders","type":"address[]"}],"name":"acceptAuthorizedReceivers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"ownable","type":"address[]"}],"name":"acceptOwnableContracts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"bytes4","name":"callbackFunc","type":"bytes4"},{"internalType":"uint256","name":"expiration","type":"uint256"}],"name":"cancelOracleRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"bytes4","name":"callbackFunc","type":"bytes4"},{"internalType":"uint256","name":"expiration","type":"uint256"}],"name":"cancelOracleRequestByRequester","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"distributeFunds","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"address","name":"callbackAddress","type":"address"},{"internalType":"bytes4","name":"callbackFunctionId","type":"bytes4"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"bytes32","name":"data","type":"bytes32"}],"name":"fulfillOracleRequest","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"address","name":"callbackAddress","type":"address"},{"internalType":"bytes4","name":"callbackFunctionId","type":"bytes4"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"fulfillOracleRequest2","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAuthorizedSenders","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainlinkToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExpiryTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"isAuthorizedSender","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onTokenTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"bytes32","name":"specId","type":"bytes32"},{"internalType":"bytes4","name":"callbackFunctionId","type":"bytes4"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"dataVersion","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"operatorRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"bytes32","name":"specId","type":"bytes32"},{"internalType":"address","name":"callbackAddress","type":"address"},{"internalType":"bytes4","name":"callbackFunctionId","type":"bytes4"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"dataVersion","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"oracleRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"ownerForward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"ownerTransferAndCall","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"senders","type":"address[]"}],"name":"setAuthorizedSenders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"targets","type":"address[]"},{"internalType":"address[]","name":"senders","type":"address[]"}],"name":"setAuthorizedSendersOn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"ownable","type":"address[]"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnableContracts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

SXT中继

JSX

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"ChainlinkCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"ChainlinkFulfilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"ChainlinkRequested","type":"event"},{"inputs":[],"name":"FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chainlinkJobId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"b","type":"bytes1"}],"name":"char","outputs":[{"internalType":"bytes1","name":"c","type":"bytes1"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"currentRequestId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAdmin","outputs":[{"internalType":"address","name":"adminAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"chainlink","type":"address"},{"internalType":"string","name":"jobId","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"},{"internalType":"string[][]","name":"data","type":"string[][]"}],"name":"queryResponse","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"callerContract","type":"address"},{"internalType":"string","name":"query","type":"string"},{"internalType":"string","name":"resourceId","type":"string"}],"name":"requestQuery","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"adminAddress","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"jobId","type":"string"}],"name":"setChainlinkJobID","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOperator","type":"address"}],"name":"setChainlinkOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_source","type":"string"}],"name":"stringToBytes32","outputs":[{"internalType":"bytes32","name":"result","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"x","type":"address"}],"name":"toAsciiString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawChainlink","outputs":[],"stateMutability":"nonpayable","type":"function"}]

更新于4 个月前