前言

在上一篇博客的基础上,本文将详细介绍Chainlink获取链下数据的方法。

本文需要在Remix上部署三个智能合约,分别是LinkToken.sol、Operator.sol和ATestnetConsumer.sol。而LinkToken合约的部署流程已在上文详细给出,这里我写一下跳转链接:【Chainlink】基于私链运行一个Chainlink预言机节点 | Keike1 (keikei99.github.io)

区块链上的用户作为数据获取方,需要从链下的数据提供方获取数据,而区块链本身不具备获取现实世界数据的能力,因此需要使用Chainlink预言机作为连接链上和链下之间的桥梁。下面我们将模拟用户获取数据的这个过程。

部署Operator.sol合约

Operator.sol

1
2
3
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
import "@chainlink/contracts/src/v0.7/Operator.sol";

部署完要记得把合约地址记录下来

1
Operator: 0xD82f08722b8e24DA3ab608AB11E230dB0E0a5829

随后调用setAuthorizedSenders方法,填自己的账户地址,注意要以数组形式传递。如

1
["0x0688AbDa371A869DcBb56d2E946A6898660ECDef"]

可以用isAuthorizedSender来判断是否设置成功

A screenshot showing Chainlink node whitelisted in Remix.

写入以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
# THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
# DO NOT USE THIS CODE IN PRODUCTION.

name = "test"
schemaVersion = 1
type = "directrequest"
# Optional External Job ID: Automatically generated if unspecified
# externalJobID = "b1d42cd5-4a3a-4200-b1f7-25a68e48aad8"
contractAddress = "YOUR_OPERATOR_CONTRACT_ADDRESS"
maxTaskDuration = "0s"
minIncomingConfirmations = 0
observationSource = """
decode_log [type="ethabidecodelog"
abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)"
data="$(jobRun.logData)"
topics="$(jobRun.logTopics)"]

decode_cbor [type="cborparse" data="$(decode_log.data)"]
fetch [type="http" method=GET url="$(decode_cbor.get)" allowUnrestrictedNetworkAccess="true"]
parse [type="jsonparse" path="$(decode_cbor.path)" data="$(fetch)"]

multiply [type="multiply" input="$(parse)" times="$(decode_cbor.times)"]

encode_data [type="ethabiencode" abi="(bytes32 requestId, uint256 value)" data="{ \\"requestId\\": $(decode_log.requestId), \\"value\\": $(multiply) }"]
encode_tx [type="ethabiencode"
abi="fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes calldata data)"
data="{\\"requestId\\": $(decode_log.requestId), \\"payment\\": $(decode_log.payment), \\"callbackAddress\\": $(decode_log.callbackAddr), \\"callbackFunctionId\\": $(decode_log.callbackFunctionId), \\"expiration\\": $(decode_log.cancelExpiration), \\"data\\": $(encode_data)}"
]
submit_tx [type="ethtx" to="YOUR_OPERATOR_CONTRACT_ADDRESS" data="$(encode_tx)"]

decode_log -> decode_cbor -> fetch -> parse -> multiply -> encode_data -> encode_tx -> submit_tx
"""

注意将以上的两个YOUR_OPERATOR_CONTRACT_ADDRESS改为你的Operator合约地址。

随后复制系统给出的externalJobId备用。

1
JobId: e22cd821-ecb1-405f-9525-d273acc8d66e

部署ATestnetConsumer合约

ATestnetConsumer.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

contract ATestnetConsumer is ChainlinkClient, ConfirmedOwner {
using Chainlink for Chainlink.Request;

uint256 private constant ORACLE_PAYMENT = (1 * LINK_DIVISIBILITY) / 10; // 0.1 * 10**18
uint256 public currentPrice;

event RequestEthereumPriceFulfilled(
bytes32 indexed requestId,
uint256 indexed price
);

/**
* Sepolia
*@dev LINK address in Sepolia network: 0x779877A7B0D9E8603169DdbD7836e478b4624789
* @dev Check https://docs.chain.link/docs/link-token-contracts/ for LINK address for the right network
*/
constructor() ConfirmedOwner(msg.sender) {
setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789);
}

function requestEthereumPrice(
address _oracle,
string memory _jobId
) public onlyOwner {
Chainlink.Request memory req = buildChainlinkRequest(
stringToBytes32(_jobId),
address(this),
this.fulfillEthereumPrice.selector
);
req.add(
"get",
"https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"
);
req.add("path", "USD");
req.addInt("times", 100);
sendChainlinkRequestTo(_oracle, req, ORACLE_PAYMENT);
}

function fulfillEthereumPrice(
bytes32 _requestId,
uint256 _price
) public recordChainlinkFulfillment(_requestId) {
emit RequestEthereumPriceFulfilled(_requestId, _price);
currentPrice = _price;
}

function getChainlinkToken() public view returns (address) {
return chainlinkTokenAddress();
}

function withdrawLink() public onlyOwner {
LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
require(
link.transfer(msg.sender, link.balanceOf(address(this))),
"Unable to transfer"
);
}

function cancelRequest(
bytes32 _requestId,
uint256 _payment,
bytes4 _callbackFunctionId,
uint256 _expiration
) public onlyOwner {
cancelChainlinkRequest(
_requestId,
_payment,
_callbackFunctionId,
_expiration
);
}

function stringToBytes32(
string memory source
) private pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}

assembly {
// solhint-disable-line no-inline-assembly
result := mload(add(source, 32))
}
}
}

这里注意将第29行的setChainlinkToken的参数改为之前部署的LinkToken合约地址

在Metamask中导入LINK代币

私有链中可以有多种代币,这里我们使用Metamask去管理我们的账户余额以方便我们在Remix部署合约。

打开Metamask,点击Assets,点击Import tokens

然后填入你的LinkToken合约地址和它的代币符号LINK就行。

利用LinkToken合约向ATestnetConsumer合约转账

这里我转入了1个LINK,根据实际需要转就可以了。转账以后,去原来部署的ATestnetConsumer合约中调用requestEthereumPrice方法。

_oracle填入Operator合约地址,_jobId填入刚刚在控制台得到的JobId。

JobId要去掉中间的横线!!!

JobId要去掉中间的横线!!!

JobId要去掉中间的横线!!!

最后点击currentPrice,ETH/USD的汇率就出现了。

至此,我们实现了在区块链中获取链下的价格数据。在实际的开发中,可以将这种单一的数据拓展为特定的业务数据,以实现区块链与预言机在特定领域的应用。

基本请求模型–单预言机工作模式 - 知乎 (zhihu.com)