こんにちは、サイオステクノロジーの和田です。
今回はこちらのドキュメントを参考に、GincoさんのAPIを動かしてみました。ドキュメントにあるINDEXERのEthereum APIを一通り動かしてみたので、順を追って説明していきたいと思います。
Gincoが提供しているIndexer APIとは
Gincoが提供しているIndexer APIでは、Webhook通知やインデックスされたトランザクション情報を提供することで、アドレスに対して発生したイベントの追跡を容易にすることが可能となっています。
このブログの目的
実際にGincoのAPIを使ってウォレットを作成し、取引の通知(Webhook)と履歴(Transfer)の確認ができることを検証していきたいと思います。
前提条件
今回はTypescriptをベースにコードを書いていきます。実行環境はNode.jsです。
下記コードをベースに変更箇所のみ記載していきます。
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 | import * as crypto from "crypto"; import fetch from "node-fetch"; const main = async () => { const key = "<YOUR API KEY>"; const secret = "<YOUR API SECRET>"; const now = Date.now(); const oneSecondAgo = now - 1000; const nonce = oneSecondAgo; console.log(now); console.log(oneSecondAgo); const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/GetWallet"; // NOTE: The JSON payload has to be sorted in alphabetical order. const body = JSON.stringify({ wallet_id: "95c1126d-d338-481e-8edb-5d66add6cfbb", }); const msg = `${nonce}${path}${body}`; const hmac = crypto.createHmac("sha256", secret); const signature = hmac.update(msg).digest("base64"); const headers = { accept: "application/json", "Content-Type": "application/json", "X-API-KEY": key, "X-API-SIGNATURE": signature, "X-API-NONCE": `${nonce}`, }; const url = "<https://web3-cloud-testnet-prd.gincoapis.com>" + path; const response = await fetch(url, { method: "POST", body: body, headers: headers, }); const resp = await response.json(); console.log(resp); }; main().catch((e) => { console.error(e); process.exit(1); }); |
APIキーに関しては、こちらのページでログインすることで確認できます。
動作検証
以下の流れで検証を行いました。
- ウォレットの作成
- アドレスの登録
- コントラクトの登録
- アセットの登録
- Webhookの検証
- トランスファーの表示
まず最初にウォレットを作成します。
ウォレットの作成
変更箇所
1 2 3 4 5 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/RegisterWallet"; const body = JSON.stringify({ name: "Alice Wallet", }); |
実行結果
1 | { wallet_id: 'a0344698-5062-40c3-9bac-bd95d5ebac79' } |
これで、ウォレットが作成できました。一覧表示で確認してみます。
ウォレットの一覧表示
変更箇所
1 2 3 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/ListWallets"; const body = JSON.stringify({}); |
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 | { wallets: [ { wallet_id: 'a0344698-5062-40c3-9bac-bd95d5ebac79', name: 'Alice Wallet', addresses: [], confirmation: '10', create_time: '2024-09-04T04:04:06.483198Z', update_time: '2024-09-04T04:04:06.483199Z' } ], pagination: { next_page_token: '' } } |
ウォレットが作成されていることが確認できました。次に、作成したウォレットにアドレスを登録していきたいと思います。
アドレス登録の事前準備として下記コードでプライベートキーとアドレスを生成します。
1 2 3 4 5 6 7 8 9 | import { ethers } from "ethers"; // 秘密鍵をランダムに生成 const wallet = ethers.Wallet.createRandom(); // 秘密鍵とアドレスを表示 console.log("Private Key:", wallet.privateKey); console.log("Address:", wallet.address); console.log("Address without checksum:", wallet.address.toLowerCase()); |
生成したプライベートキーとアドレスはなくさないようにメモしておいてください。
では、ウォレットに生成したアドレスを登録していきます。
アドレスの登録
変更箇所
1 2 3 4 5 6 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/RegisterAddress"; const body = JSON.stringify({ address: "0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e", wallet_id: "a0344698-5062-40c3-9bac-bd95d5ebac79", }); |
実行結果
1 |
アドレスを登録できたかどうかウォレットを表示して確認していきます。
ウォレットの表示
変更箇所
1 2 3 4 5 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/GetWallet"; const body = JSON.stringify({ wallet_id: "a0344698-5062-40c3-9bac-bd95d5ebac79", }); |
実行結果
1 2 3 4 5 6 7 8 9 10 | { wallet: { wallet_id: 'a0344698-5062-40c3-9bac-bd95d5ebac79', name: 'Alice Wallet', addresses: [ '0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e' ], confirmation: '10', create_time: '2024-09-04T04:04:06.483198Z', update_time: '2024-09-04T04:04:06.483199Z' } } |
アドレスが追加されたことを確認することができました。
次にコントラクトを作成して登録していきたいと思います。
事前準備として、Remixなどでコントラクトを事前にテストネットにデプロイしておいてください。今回はERC721のコントラクトで動作確認していきます。
コントラクトの登録
変更箇所
1 2 3 4 5 6 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/RegisterContract"; const body = JSON.stringify({ contract_address: "0xa3292990a1ce2287d53b80e23727877a23c29c60".toLowerCase(), contract_type: "CONTRACT_TYPE_ERC721", }); |
実行結果
1 |
コントラクトが登録できたか確認します。
コントラクトの確認
変更箇所
1 2 3 4 5 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/GetContract"; const body = JSON.stringify({ contract_address: "0xbBd6212581C0900fe9e253eb054AC93b7BDFed44".toLowerCase(), }); |
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "contract": { "contract_address": "0xa3292990a1ce2287d53b80e23727877a23c29c60", "contract_type": "CONTRACT_TYPE_ERC721", "contract_metadata": { "erc20": null, "erc721": { "name": "MyToken", "symbol": "MTK" }, "erc1155": null } } } |
コントラクトが登録されたことを確認することができました。
続いてアセットのインポートを試したいと思います。アセットとはERC20トークンやERC721のNFTのことです。
アセットのインポート
変更箇所
1 2 3 4 5 6 7 8 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/ImportAsset"; const body = JSON.stringify({ address: "0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e", asset_type: "ASSET_TYPE_ERC721", contract_address: "0xa3292990a1ce2287d53b80e23727877a23c29c60", token_id: "0", }); |
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 | {} { assets: [ { wallet_id: 'a0344698-5062-40c3-9bac-bd95d5ebac79', address: '0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e', asset_type: 'ASSET_TYPE_ERC721', balance: '0', asset_metadata: [Object] } ], pagination: { next_page_token: '' } } |
アセットを追加することができました。一覧で表示してみます。
アセットの一覧表示
変更箇所
1 2 3 4 5 6 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/ListAssets"; const body = JSON.stringify({ asset_type: "ASSET_TYPE_ERC721", wallet_id: "a0344698-5062-40c3-9bac-bd95d5ebac79", }); |
実行結果
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 | { "assets": [ { "wallet_id": "a0344698-5062-40c3-9bac-bd95d5ebac79", "address": "0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e", "asset_type": "ASSET_TYPE_ERC721", "balance": "1", "asset_metadata": { "erc20": null, "erc721": { "contract_address": "0xa3292990a1ce2287d53b80e23727877a23c29c60", "name": "MyToken", "symbol": "MTK", "token_id": "0", "token_uri": "ipfs://testtesttest.json", "token_data": "" }, "erc1155": null } } ], "pagination": { "next_page_token": "" } } |
続いて、Webhookの検証を行いたいと思います。
事前準備として、通知を受け取るためのサーバーを用意する必要があります。
Webhookエンドポイントの事前準備
今回は、EC2上にNginxとPythonのFlaskでWebhookエンドポイント用のサーバーを立てました。
/webhookに送信されたPOSTリクエストを表示するように構築しました。
下記Nginxのコンフィグ
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 | user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; include /etc/nginx/conf.d/*.conf; server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; location /webhook { proxy_pass <http://127.0.0.1:5000>; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } } |
下記Pythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 | from flask import Flask, request app = Flask(__name__) @app.route("/webhook", methods=['POST']) def webhook(): data = request.get_data(as_text=True) headers = request.headers print(f"Received headers\\n{headers}") print(f"Received data\\n{data}") response_text = f"Headers: {headers}\\nData: {data}" return response_text |
次に、作成したサーバーのエンドポイントをWebhookに登録していきます。
Webhookの登録
変更箇所
1 2 3 4 5 6 7 8 9 | const ipAddressPort = "<YOUR IPADDRESS PORT>"; const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/RegisterWebhook"; const body = JSON.stringify({ confirmation: 10, wallet_id: "a0344698-5062-40c3-9bac-bd95d5ebac79", webhook_endpoint: `http://${ipAddressPort}/webhook`, }); |
実行結果
1 | { webhook_id: '35241b70-4e23-4b6f-a9d6-6067f11848e0' } |
Webhookを登録することができました。一覧表示で確認してみます。
Webhookの一覧表示
変更箇所
1 2 3 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/ListWebhooks"; const body = JSON.stringify({}); |
実行結果
1 2 3 4 5 6 7 8 9 10 | { webhooks: [ { webhook_id: '35241b70-4e23-4b6f-a9d6-6067f11848e0', wallet_id: 'a0344698-5062-40c3-9bac-bd95d5ebac79', confirmation: '10', webhook_endpoint: 'http://<YOUR IPADDRESS PORT>/webhook' } ] } |
これで、Webhookを登録できたことが確認できました。次に、Webhookで送信されるリクエストのヘッダーを追加したいと思います。
WebhookHeaderの登録
変更箇所
1 2 3 4 5 6 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/RegisterWebhookHeader"; const body = JSON.stringify({ header_key: "Test-Header", header_value: "Test-Value", }); |
実行結果
1 | { webhook_header_id: '0be1968a-3136-4e5d-968c-9c2f3ed97cce' } |
テスト用にheader_keyとheader_valueを設定しました。これでWebhookエンドポイントで通知を受け取った際にヘッダーが追加されていることが確認できます。ヘッダーが登録されたか確認します。
WebhookHeaderの一覧表示
変更箇所
1 2 3 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/ListWebhookHeaders"; const body = JSON.stringify({}); |
実行結果
1 2 3 4 5 6 7 8 9 10 | { webhook_headers: [ { webhook_header_id: '0be1968a-3136-4e5d-968c-9c2f3ed97cce', webhook_id: '', header_key: 'Test-Header', header_value: 'Test-Value' } ] } |
続いて、Webhookの動作確認を行いたいと思います。MetaMaskなどで他のウォレットから今回作成したウォレットに送金して、結果が通知されるか確認します。
Webhook動作確認
他のウォレットから作成したウォレットに送金したところ、下記のとおりWebhookで通知が来たことを確認できました。
1 2 3 4 5 6 7 8 9 10 11 12 13 | Received headers Host: 13.236.177.75 X-Real-Ip: 34.146.200.240 X-Forwarded-For: 34.146.200.240 X-Forwarded-Proto: http Connection: close Content-Length: 391 User-Agent: Go-http-client/1.1 Test-Header: Test-Value Accept-Encoding: gzip Received data {"webhook_id":"35241b70-4e23-4b6f-a9d6-6067f11848e0","webhook_endpoint":"http://<YOUR IPADDRESS PORT>/webhook","notification_type":"NOTIFICATION_TYPE_EVM_TRANSACTION_UPDATED","event":{"transaction_hash":"0x06b31dd924f1f1650e4952add792072a9618a4e3745c5c2e02cc36b6e362daea","address":"0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e","confirmation":1,"wallet_id":"a0344698-5062-40c3-9bac-bd95d5ebac79"}} |
先ほど設定したTest-Headerも追加されていることが確認できました。
最後にトランスファーの一覧表示を確認してみたいと思います。こちらで取引履歴を確認することができます。
トランスファーの一覧表示
変更箇所
1 2 3 4 5 | const path = "/gincoinc.web3cloud.ethereum.gateway.v1.GatewayService/ListTransfers"; const body = JSON.stringify({ wallet_id: "a0344698-5062-40c3-9bac-bd95d5ebac79", }); |
実行結果
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 | { "transfers": [ { "transaction_hash": "0xcd515bc6b288bd085051bd174aa04886d3058a8ceffe0d477b22440bec62b287", "asset_type": "ASSET_TYPE_ERC721", "nonce": "1063", "from": "0x0000000000000000000000000000000000000000", "to": "0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e", "value": "1", "transaction_fee": "0.001203297764048282", "transfer_state": "TRANSFER_STATE_SUCCESS", "block_number": "6659705", "block_timestamp": "1725864948", "transfer_metadata": { "erc20": null, "erc721": { "contract_address": "0xa3292990a1ce2287d53b80e23727877a23c29c60", "name": "MyToken", "token_id": "0", "symbol": "MTK", "token_uri": "ipfs://testtesttest.json", "token_data": "" }, "erc1155": null } }, { "transaction_hash": "0x06b31dd924f1f1650e4952add792072a9618a4e3745c5c2e02cc36b6e362daea", "asset_type": "ASSET_TYPE_ETH", "nonce": "7", "from": "0x68aabf716de793b59fd83265b42aa053be1c0161", "to": "0xcfd0da83f1764c04b65fc9222d2ddcc0a8ef819e", "value": "0.001", "transaction_fee": "0.000051917709102", "transfer_state": "TRANSFER_STATE_SUCCESS", "block_number": "6659301", "block_timestamp": "1725858912", "transfer_metadata": { "erc20": null, "erc721": null, "erc1155": null } }, ], "pagination": { "next_page_token": "" } } |
取引の履歴を確認することができました。
今回はMetaMaskで外部のウォレットからの送金も行いましたが、その履歴もすべて表示されることが確認できました。
終わりに
今回は、GincoのINDEXER APIを一通り動かしてみました。実装してみた感想としては、Webhookの通知設定ができるところが便利だと思いました。また、INDEXER APIを使って、仮想通貨の取引情報を簡単に管理できることが実感できました。