🌊

【Hokusai API】 NFTをガス代なしでTransferするフロントエンドをReactで作る

2021/10/12に公開

はじめに

今回は、Hokusai APIを利用して、ガス代なし(Meta Transaction)でNFTをTransferをするサンプルを紹介します。
この機能を実装すれば、実装したサービスを利用するユーザが、ガス代を負担することなく、サービスを利用することができるようになります。(つまり暗号資産を持たないユーザ向けのサービスを展開することができます)

今回使うサンプルコードはこちらとなります↓
https://github.com/0xhokusai/hokusai-api-client-sample

Hokusai APIとは

https://hokusai.app/
Hokusai APIは、NFTのMint(発行)、Transfer(移転)、Burn(消却)、Royaltyの設定、Metadata情報の取得などの機能をAPIとして提供しているサービスです。
自分でコントラクトを書かずにNFTのサービスを作ることができます。

API Keyを発行する

Hokusai APIではTestnet用のAPI Keyを無料で配布しています。
以下のフォームから利用申請を行うことができます。

https://ir9l8pcvcmm.typeform.com/to/xSbuj2WA

Metamaskの実装

今回はWalletとして、一般的であるMetamaskを利用します。
https://docs.metamask.io/guide/#why-metamask

ネットワーク設定

今回はPolygonのTestnetであるMumbaiを利用するので、以下のようにネットワークを設定します。

const networkParam = {
  chainId: '0x13881',
  chainName: 'Mumbai Testnet',
  nativeCurrency: { name: 'Matic', symbol: 'MATIC', decimals: 18 },
  rpcUrls: ['https://rpc-mumbai.matic.today'],
  blockExplorerUrls: ['https://mumbai.polygonscan.com/'],
};

注意するべきところはchainIdは10進数ではなく16進数の文字列で設定する必要があります。
ネットワーク情報についてはPolygon公式のこちらのサイトに記述されています。
https://docs.polygon.technology/docs/develop/network-details/network

Metamaskとの接続

続いて、Metamaskを接続しProviderを取得する関数を定義します。
wallet_addEthereumChainメソッドを利用することで、ネットワーク切り替えのモーダルを表示させることができます(ユーザがそのネットワークをMetamaskに登録してない場合、同時にネットワークを登録させることができます。)

import { ethers } from 'ethers';

async function connectMetamask() {
  // Initialize Metamask
  const provider = await window.ethereum
    .request({
      method: 'eth_requestAccounts',
      params: [{ eth_accounts: {} }],
    })
    .then(() => new ethers.providers.Web3Provider(window.ethereum))
    .catch((error: Error) => {
      console.log(error);
    });

  // Set network
  await window.ethereum.request({
    method: 'wallet_addEthereumChain',
    params: [networkParam],
  });
  return provider;
}

Hokusai APIでNFTをTransferする

Hokusai APIでNFTをTransferする手順は以下の通りです。

  1. Transactionのデータを作成
  2. 1.のデータをMetamaskで署名
  3. 1.で作成したデータと2.で作成した署名をHokusai APIにPost

また、Hokusai APIででMeta Transactionを利用する際には、transferエンドポイントを利用します。
https://docs.hokusai.app/docs/hokusai-api/b3A6MjA5NTQ3NzM-transfer-a-nft-with-meta-transaction
Bodyに必要なデータは、以下の通りです。

  • from: Transaction送信者のアドレス
  • to: Hokusai APIのNFTコントラクトのアドレス
  • value: 送金する金額(=0)
  • gas: ガス
  • nonce:Transaction送信者のNonce
  • data:Transactionのデータ(NFTのコントラクトで実行するデータ)
  • signature: ユーザがMetamaskで署名したdata
{
  "request": {
    "from": string,
    "to": string,
    "value": number,
    "gas": number,
    "nonce": number,
    "data": string
  }
}

1. Transactionデータの作成

Transactionのデータを作成します。
Transactionのデータ構造は以下の通りです。

{
  from: string;
  to: string;
  value: number;
  gas: number;
  nonce: number;
  data: string;
};

まず初めに、dataに入れるNFTのコントラクトで実行したい関数のデータを作成します
ABIはこちらのものを使います。
https://github.com/0xhokusai/hokusai-api-client-sample/blob/main/src/abis/ERC721WithRoyaltyMetaTx.json

import { ethers } from 'ethers';
// https://github.com/0xhokusai/hokusai-api-client-sample/blob/main/src/abis/ERC721WithRoyaltyMetaTx.json
import HokusaiAbi from '../abis/ERC721WithRoyaltyMetaTx.json';

// 接続しているウォレットアドレスを取得
const signer = provider.getSigner();
const from = await signer.getAddress();

// ABIの読み込み
const hokusaiInterface = new ethers.utils.Interface(HokusaiAbi.abi);

// データの作成
const data = hokusaiInterface.encodeFunctionData('transferFrom', [
   from,
   toAddress,
   tokenId,
]);

const hokusaiInterface = new ethers.utils.Interface(HokusaiAbi.abi);でAbiを読み込み、encodeFunctionDataで関数のデータを作成しています。
ここで、encodeFunctionDataの引数は、

  • from: ユーザのウォレットアドレス
  • toAddress: NFTの移転先のウォレットアドレス
  • tokenId: 移転したいNFTのID

となります。

続いて他のパラメータを設定します。

import ForwarderAbi from '../abis/MinimalForwarder.json';

type Message = {
  from: string;
  to: string;
  value: number;
  gas: number;
  nonce: number;
  data: string;
};


// forwarderコントラクトの読み込み
const forwarder = new ethers.Contract(
  forwarderAddress, // 0x0E285b682EAF6244a2AD3b1D25cFe61BF6A41fc3 ← テストネット用
  ForwarderAbi.abi,
  provider
);

const message: Message = {
  from,
  to: contractAddress, // 0x73b5373a27f4a271c6559c6c83b10620acde9a2a ← テストネット用
  value: 0,
  gas: 1e6,
  nonce: (await forwarder.getNonce(from)).toNumber(),
  data,
};

コントラクトアドレスに関して、Hokusai APIのMumbai Testnet環境では以下のように設定します。
forwarderAddress: 0x0E285b682EAF6244a2AD3b1D25cFe61BF6A41fc3
contractAddress: 0x73b5373a27f4a271c6559c6c83b10620acde9a2a

また、forwarder.getNonce(from)でTransactionに必要なNonceをForwarderから取得します。
dataは、先程encodeFunctionDataで作成したものを利用します。

2. Metamaskでデータに署名

署名に必要な手順は以下の通りです。

  1. 署名データを作成(先程作成したmessageを利用)
  2. Metamaskの署名モーダルを表示し、ユーザに署名させる

署名データの作成

signTypedData_v4の署名データを作成します。

https://docs.metamask.io/guide/signing-data.html#sign-typed-data-v4

署名データを作成する関数createTypedDataV4()を定義します。

const EIP712DomainType = [
  { name: 'name', type: 'string' },
  { name: 'version', type: 'string' },
  { name: 'chainId', type: 'uint256' },
  { name: 'verifyingContract', type: 'address' },
];

const ForwardRequestType = [
  { name: 'from', type: 'address' },
  { name: 'to', type: 'address' },
  { name: 'value', type: 'uint256' },
  { name: 'gas', type: 'uint256' },
  { name: 'nonce', type: 'uint256' },
  { name: 'data', type: 'bytes' },
];

// https://eips.ethereum.org/EIPS/eip-712
export function createTypedDataV4(
  chainId: number,
  ForwarderAddress: string,
  message: Message
) {
  const TypedData = {
    primaryType: 'ForwardRequest' as const,
    types: {
      EIP712Domain: EIP712DomainType,
      ForwardRequest: ForwardRequestType,
    },
    domain: {
      name: 'MinimalForwarder',
      version: '0.0.1',
      chainId,
      verifyingContract: ForwarderAddress,
    },
    message,
  };
  return TypedData;
}

次に、createTypedDataV4()を利用して、署名データを作成します。
messageは1つ前で作成したmessageとなります。

const { chainId } = await provider.getNetwork();

const typedData = createTypedDataV4(
  chainId,
  forwarderAddress, // 0x0E285b682EAF6244a2AD3b1D25cFe61BF6A41fc3
  message
);

Metamaskの署名モーダルを表示し、ユーザに署名させる

署名したデータをsignatureという変数に格納しています。

const signature = await provider.send('eth_signTypedData_v4', [
  from,
  JSON.stringify(typedData),
]);

3. Post!

最後にHokusai APIに作成したmessagesignatureをPostします。
contractIdapiKeyは、

const contractId = xxxxx
const apiKey = yyyyy

result = await fetch(
  `https://mumbai.hokusai.app/v1/nfts/${contractId}/transfer?key=${apiKey}`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ request: { ...message, signature } }),
  }
)
        .then((res) => res.json())

まとめ

Hokusai APIを利用して、ガス代なし(Meta Transaction)でNFTをTransferをするためには次の手順が必要です。

  1. Transactionデータの作成
  2. 署名データの作成&Metamaskでの署名
  3. Hokusai APIにPost

宣伝

Hokusaiを提供する日本モノバンドル株式会社では、エンジニアを採用中です!
ぜひフルリモートで、スピード感や大きな変化を楽しみながらぜひ働いてみませんか?

https://www.notion.so/0xhokusai/Backend-engineer-aabdbbbb48584113854e9e8102f13d6b

また、ブロックチェーン技術に関するニュースレターを週1回で配信しています。
ぜひご登録お願いします!
https://0xhokusai.substack.com/

この記事を書いた人


Tarumi - CTO

https://y.at/🔥🍞🔥🍞🔥

Discussion