こんにちは。
PS/SLの佐々木です。
今回はブロックチェーンからweb2のAPI(Get以外)をcallする方法をまとめたいと思います。
ブロックチェーンからAPIをCallする方法
通常のweb2.0では他のAPIをCallする際にはaxiosといったようなHTT’クライアントライブラリが用意されていますが、ブロックチェーンにはそのようなものがありません。
しかしweb2の世界のデータをブロックチェーンで使用したいということがあると思います。
そのような際にはchainlinkというサービスを使用することでWeb2のAPIをCallすることができます。
今回は検索などで使用するPOSTのリクエストをしたい場合の方法を紹介します。
POST Data to an API
POSTのリクエストを送るにはChainlink FunctionsのPOST Data to an APIのリファレンスにどのように実装するか記載があります。
今回の記事で紹介する方法はRemix上でSepoliaのtestnetを使用します。
事前準備としてsepoliaのTokenとchainlinkで使用するLINK Tokenを取得しておいてください。
こちらから取得できます。
実装
chainlinkのsubscriptionを作成する
上記のリンクからSubscriptionを作成します。
Create Subscriptionを押下します。
メールアドレスとSubscription nameを設定します。
metamaskで順番に承認していきます。
続いて先ほど取得したLINKをSubscriptionに入金します。
こちらも順次承認していきます。
ここで入金したLINKはchainlink functionを呼び出すたびに消費されていき、LINKがなくなるとchainlink functionを呼び出せなくなってしまいますので注意して定期的に入金する運用をしてください。
最後にConsumerを追加します。
ここでいうConsumerとは先ほどデプロイしたスマートコントラクトのアドレスです。
もしLINKがなくなった場合にはActionからFund subscriptionを押下し、入金します。
スマートコントラクトを再デプロイした場合にはAdd Consumerから追加します。
またここにあるSubscription IDをスマートコントラクトのsubscriptionIdの変数に追加して、デプロイしてください。
solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/FunctionsClient.sol";
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/libraries/FunctionsRequest.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract FunctionsConsumerExample is FunctionsClient, ConfirmedOwner {
using FunctionsRequest for FunctionsRequest.Request;
bytes32 public s_lastRequestId;
bytes public s_lastResponse;
bytes public s_lastError;
error UnexpectedRequestID(bytes32 requestId);
event Response(bytes32 indexed requestId, bytes response, bytes err);
constructor(
address router
) FunctionsClient(router) ConfirmedOwner(msg.sender) {}
function sendRequest() external onlyOwner returns (bytes32 requestId) {
FunctionsRequest.Request memory req;
uint64 subscriptionId = subscriptionId; // 任意のsubscriptionIdに書き換えてください
uint32 gasLimit = 300000;
// <https://docs.chain.link/chainlink-functions/supported-networks>
// リンクからデプロイ先のnetworkのdonIDに書き換えてください
bytes32 donID = 0x66756e2d657468657265756d2d7365706f6c69612d3100000000000000000000;
// route 0xb83E47C2bC239B3bf370bc41e1459A34b41238D0
string memory source =
// urlは今回適当ですので、叩きたいAPIのエンドポイントを設定してください
"const url = '<https://example.com>';"
"const requestBody = {"
"name": "test",
"q": "SIOS PS/SL",
"};"
"const apiResponse = await Functions.makeHttpRequest({"
"url: url,"
"method: 'POST',"
"data: requestBody,"
"headers: { 'Content-Type': 'application/json' }"
"});"
"if (apiResponse.error) {"
"throw Error(`Request failed: ${apiResponse.error}`);"
"}"
"const { data } = apiResponse;"
"return Functions.encodeString(JSON.stringify(data));";
req.initializeRequestForInlineJavaScript(source);
s_lastRequestId = _sendRequest(
req.encodeCBOR(),
subscriptionId,
gasLimit,
donID
);
return s_lastRequestId;
}
/**
* @notice Send a pre-encoded CBOR request
* @param request CBOR-encoded request data
* @param subscriptionId Billing ID
* @param gasLimit The maximum amount of gas the request can consume
* @param donID ID of the job to be invoked
* @return requestId The ID of the sent request
*/
function sendRequestCBOR(
bytes memory request,
uint64 subscriptionId,
uint32 gasLimit,
bytes32 donID
) external onlyOwner returns (bytes32 requestId) {
s_lastRequestId = _sendRequest(
request,
subscriptionId,
gasLimit,
donID
);
return s_lastRequestId;
}
/**
* @notice Store latest result/error
* @param requestId The request ID, returned by sendRequest()
* @param response Aggregated response from the user code
* @param err Aggregated error from the user code or from the execution pipeline
* Either response or error parameter will be set, but never both
*/
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory err
) internal override {
if (s_lastRequestId != requestId) {
revert UnexpectedRequestID(requestId);
}
s_lastResponse = response;
s_lastError = err;
emit Response(requestId, s_lastResponse, s_lastError);
}
function bytesToString(bytes memory byteData) public pure returns (string memory) {
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(2 + byteData.length * 2);
str[0] = "0";
str[1] = "x";
for (uint i = 0; i < byteData.length; i++) {
str[2+i*2] = alphabet[uint(uint8(byteData[i] >> 4))];
str[3+i*2] = alphabet[uint(uint8(byteData[i] & 0x0f))];
}
return string(str);
}
}
今回デプロイするスマートコントラクトはこちらです。
sendRequest
内source
という変数の中にString形式でJavaScriptのソースコードを格納し、Chainlink側に送るとソースコードを実行してくれます。
Node.jsで実行可能なソースコードであればChainlink側で実装することができるため、計算に時間のかかる処理などで使用することもできます。
ただし外部のライブラリは使用することができないため、HTTPリクエストを送る場合にはaxiosではなく、Functions.makeHttpRequest
を使用します。
詳細なリファレンスはこちらです。
終わりに
今回はChainlink Functionを使用してPOSTのリクエストを送る方法について説明しました。
この方法を使用すればスマートコントラクト外で計算を行いたい場合などにも応用可能になります。
またソースコードをAPIの引数として渡すこともできる(Chainlink functionsのチュートリアルはその方法を紹介しています。)ためスマートコントラクトの容量もより減らすことができます。
是非Chainlink Functionを使用してより自由度の高いスマートコントラクト開発をしていきましょ!