OpenSea のアセットの売買を公式SDK(opensea-js)を使ってプログラムから行う方法を簡単に解説してみたいと思います。
OpenSea SDK
OpenSea のアセットを売買するときには、トランザクションを発行したり色々な処理を正確に行う必要があります。 GUIからやれば、ボタンをぽちぽちするだけですが、プログラムから売買する場合は、何かと面倒なことが多くなってきます。
そこで公式から色々な低水準な処理をやってくれる JavaScript の公式SDKが提供されております。 Uniswap なんかにも SDK がありますね。
ただ、なんだか知りませんがこのSDKの使い方のサンプルが全然見つからないんですよね。公式ページの説明もいまいち丁寧さに欠けます。
そういうわけで、簡単に使い方を紹介してみます。
- SDK の GitHub
- SDK Document
サンプルコード
というわけでサンプルコードです。
import { OpenSeaPort, Network } from 'opensea-js';
import { OrderSide, SaleKind } from 'opensea-js/lib/types.js';
import ethers from 'ethers';
import { PrivateKeyWalletSubprovider} from '@0x/subproviders';
import RpcSubprovider from 'web3-provider-engine/subproviders/rpc.js';
import Web3ProviderEngine from 'web3-provider-engine';
import dotenv from 'dotenv';
dotenv.config();
// config
const host = process.env.HOST_TESTNET;
const privateKey = process.env.PRIVATE_KEY_TESTNET;
const token_address = process.env.TOKEN_ADDRESS_TESTNET;
const token_id = process.env.TOKEN_ID_TESTNET;
// Create 0x provider
const walletSubprovider = new PrivateKeyWalletSubprovider(privateKey, 4); // 4: Rinkeby
const rpcSubprovider = new RpcSubprovider({
rpcUrl: host
});
const providerEngine = new Web3ProviderEngine();
providerEngine.addProvider(walletSubprovider);
providerEngine.addProvider(rpcSubprovider);
providerEngine.start();
const seaport = new OpenSeaPort( providerEngine, {
networkName: Network.Rinkeby
});
const wallet = new ethers.Wallet(privateKey);
const main = async () => {
console.info(`TOKEN : ${token_address}`);
console.info(`ID : ${token_id}`);
// NOTE: This demo is only valid for fixed price sell order.
const { orders, count } = await seaport.api.getOrders({
asset_contract_address: token_address,
token_id: token_id,
sale_kind: SaleKind.FixedPrice, // 0
side: OrderSide.Sell // 1
})
// Get the cheapest order.
const orders_sorted = orders.sort((a, b) => a.basePrice.gt(b.basePrice) ? 1 : -1);
console.info("Sell prices");
for ( const order of orders_sorted ) {
console.info(ethers.utils.formatUnits(order.basePrice.toString()));
}
const order = orders_sorted[0];
// Fulfill
const txhash = await seaport.fulfillOrder({
order: order,
accountAddress: wallet.address
});
console.info(`Hash : ${txhash}`);
}
main();
動作
テストネットで動くようにしておきました。(OpenSea は Rinkeby にコントラクトがデプロイされています。)
- OpenSea でトークン(NFT)のアドレスと、ID を指定し、特定のアセットをターゲットにします。
- 該当アセットに売り注文が出ているかどうかを確認し、売り注文が出ている場合その値段で買います。
- 買い注文のために、指定したウォレットからトランザクションを発行します。
- 専門的に言うと、OpenSea でメイカーが出しているオーダーを、テイカーとしてテイクするコードです。
ライブラリ
- opensea-js
- OpenSea SDK です
- web3-provider-engine
- 元は MetaMask のために開発されたライブラリです。よく知らないんですが、元は web3.js に Infura などとやりとりする機能が欠けていたので作られたらしいですね。現在は開発が止まっている感じなのですが、OpenSea SDK はブロックチェーンとやりとりするときにこのライブラリが必要です。
- @0x/subproviders
- 0x が開発した、web3-provider-engine 用のインターフェースです。
簡単な解説
以下簡単な解説です。
dotenv (config)
dotenv というライブラリを使って、秘密鍵などを取得しています。
dotenv はまあ一番使われている定番ライブラリだと思います。.env
というファイルから環境変数を設定してくれます。
- 秘密鍵をコードに直接埋め込んでいたりする人もいますが絶対にやめましょうね。
- ごく一時的なものでも秘匿すべき情報は、必ず別ファイルにして読み込みましょう。
.env
ファイルは絶対にチェックインしないように、.gitignore
に登録します。基本ですね。
Provider
トランザクションを発行するための Provider などを準備します。
- 私は、Infura で、Rinkeby の RPC 用の URL を取得して使っています。
- テストネットの場合、
PrivateKeyWalletSubprovider
のコンストラクタにネットワークを指定する必要があるので注意してください。- メインネットであれば問題ないですが、Rinkeby用に
4
を渡してやる必要があります。 - ここで手間取って、ソースコードを読んでようやく解決しましたよ。案外テストネットで動かす方が難しかったりするものです。
- メインネットであれば問題ないですが、Rinkeby用に
SeaPort
SDK のメインのクライアントとなります。
オーダーの取得 (getOrders)
OpenSea に出ているメイカーのオーダーを取得しています。
getOrders
へクエリ用のオブジェクトを渡すことができます。- 今回は、クエリ条件で FixedPrice の売り注文のみを対象にしています。
- つまりダッチオークションには対応していません。ダッチオークションの場合価格の計算が煩雑だからです。(Base price 等を使って計算をする必要があり)
- ダッチオークションも考慮すると一番安いオーダーを取得しているコードの部分を少し複雑にする必要があります。
- まあ実用上は、Fixed Price の場合がほとんどですし、事故を防ぐためにも Fixed Price のみに絞ってもいいかもしれません。
テイカー注文の発行 (fulfillOrder)
メイカーのオーダーをテイクします。
- Provider の設定さえちゃんとできていれば、ほんの数行のコードでテイカー注文を出せます。
- ガス代の設定などがされていませんが、それはよろしくやってくれます。
エラー処理
わかりやすくごく最小限のコードにしてあるので、実用上はエラー処理など行ってください。
コードの実行
普通の Node で実行することは出来ません。トランスパイラを使いましょう。ts-node を使うのがおすすめです。
こんな感じで実行できます。
npx ts-node index.js
感想
コードが非常に簡潔なところをみてもわかるように、全体的によろしくやってくれる SDK です。 個人的にはこれはあまり好きではないところです。トランザクション発行のためのあれこれをやってくれて、 トランザクション発行は各自でっていう感じの方が良いですよね。あれこれSDKに機能を詰め込むのはあまり良い設計とは思われません。
例えば Flashbots を使いたいとか、Gas 代を柔軟に設定したいとかそういうことを考えると自分で頑張らなくてはいけなくなります。 まあ Rarible なんかまともな SDK もないんでそれに比べたらありがたいですね。
ちなみに Seaport に Gas 代の設定なんかもあるんですが、ちょっと使い方がわかりませんでした。