Seaport Protocol を試す

はコントラクトを管理しているリポジトリ
JS の SDK は含まれていない

JS の SDK はこっちらしい
まだ npm に公開されていないらしく
$ yarn add https://github.com/ProjectOpenSea/seaport-js
しないといけない

import { Seaport } from "seaport-js";
import { ethers } from "ethers";
async function main(): Promise<void> {
const provider = ethers.getDefaultProvider();
const seaport = new Seaport(provider);
}
main();
README に乗ってるコードをコピペしてきてもエラーになる...

第二引数を空の引数にしてもエラーになる...

いつの間にか npm にリリースされていた

動かないなあと思ったら
import { SeaportSDK } from "seaport-js";
が正しい

↑のインタフェースの変更は 1.1.0 かららしく、1.0.2 を試す
ちなみに 1.1.0 のソースコードは GitHub に push されていない

と思ったらそんなバージョンはなかった...

???

あー今気づいたけど @opensea/seaport-js
と seaport-js
は別らしい...

opensea-js
で Seaport がサポートされてた
ライブラリの使い方はこのコードを参考にすると良さそう

opensea-js
からリスティングしてみた
トランザクションの一覧を見ると setApprovalForAll
のトランザクションしかない
opensea-js
や @opensea/seaport-js
の実装を見るに
-
@opensea/seaport-js
で order の署名をする -
opensea-js
で OpenSea のバックエンドに署名を送信
という役割分担になっているっぽい

listing されているトークンを購入するときにトランザクションが発行される
送信先は Seaport のコントラクト

シグネチャは 0xfb0f3ee1
で呼び出されたメソッドは fulfillBasicOrder
っぽい
デフォルトでは @opensea/seaport-js
は NO_CONDUIT
を conduitKey
に指定するので、Seaport だけで転送が行われる

Conduit Controller は ImmutableCreate2Factory を使ってデプロイされている
実装
コントラクト

Conduit Controller で作成された Conduit コントラクトはこれ
conduit : 0xf3f8b809f0313b270b825642f106149cdc26cd09
conduitKey : A76F9817813C745DC9B678A7363DC774B36DE0D9000000000000000000000000

@opensea/seaport-js
が ethers と相性が悪い理由がわかった
JsonRpcProvider
の getSigner
は JsonRpcSigner
が返されてしまうので、Wallet クラスのインスタンスを provider と紐付けていても意味がない
JsonRpcSigner
はトランザクションを投げる場合、this.provider.send("eth_sendTransaction"
してしまうのでローカルにある秘密鍵を使えない

@opensea/seaport-js
が ethers と仲良くするには Signer をコンストラクタで受け取らないといけない
今の provider に依存する仕組みだと @truffle/hdwallet-provider
などの eth_sendTransaction
に hook してトランザクションを投げる provider も使う必要がある

import { Seaport } from "@opensea/seaport-js";
import { ItemType } from "@opensea/seaport-js/lib/constants";
import { ethers } from "ethers";
import HDWalletProvider from "@truffle/hdwallet-provider";
import { OpenSeaAPI } from "opensea-js";
const RPC_URL = process.env["RPC_URL"]!;
const PRIVATE_KEY = process.env["PRIVATE_KEY"]!;
async function main(): Promise<void> {
const api = new OpenSeaAPI({});
const hdWalletProvider = new HDWalletProvider({
privateKeys: [PRIVATE_KEY],
providerOrUrl: RPC_URL,
});
const provider = new ethers.providers.Web3Provider(hdWalletProvider);
const address = await provider.getSigner().getAddress();
const seaport = new Seaport(provider, {
conduitKeyToConduit: {
"0xA76F9817813C745DC9B678A7363DC774B36DE0D9000000000000000000000000":
"0xf3f8b809f0313b270b825642f106149cdc26cd09",
},
});
const { executeAllActions } = await seaport.createOrder(
{
offer: [
{
itemType: ItemType.ERC721,
token: "0xa499f7d121dd0daa7e65b273a773cdca45a68cb0",
identifier: "0",
},
],
consideration: [
{
amount: ethers.utils.parseEther("0.000001").toString(),
recipient: address,
},
],
conduitKey:
"0xA76F9817813C745DC9B678A7363DC774B36DE0D9000000000000000000000000",
},
address
);
const order = await executeAllActions();
console.log("order", order);
const orderV2 = await api.postOrder(order, {
protocol: "seaport",
side: "ask",
});
console.log("orderV2", orderV2);
}
main();
process.on("unhandledRejection", (reason) => {
console.error(reason);
process.exit(1);
});
ためしに投げてみるとエラーになった
Error: API Error 400: You provided an invalid conduit key: 0xA76F9817813C745DC9B678A7363DC774B36DE0D9000000000000000000000000, please use OpenSea's conduit key: 0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000 for order placement
0xA76F9817813C745DC9B678A7363DC774B36DE0D9000000000000000000000000
は使えなくて 0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000
しかだめらしい

署名時に表示されるパラメータの説明

0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000
に対応するコントラクトは 0x1e0049783f008a0085193e00003d00cd54003c71
だった

import { Seaport } from "@opensea/seaport-js";
import { ItemType } from "@opensea/seaport-js/lib/constants";
import { ethers } from "ethers";
import HDWalletProvider from "@truffle/hdwallet-provider";
import { OpenSeaAPI } from "opensea-js";
import {
CONDUIT_KEYS_TO_CONDUIT,
CROSS_CHAIN_DEFAULT_CONDUIT_KEY,
} from "../node_modules/opensea-js/lib/constants";
const RPC_URL = process.env["RPC_URL"]!;
const PRIVATE_KEY = process.env["PRIVATE_KEY"]!;
async function main(): Promise<void> {
const api = new OpenSeaAPI({});
const hdWalletProvider = new HDWalletProvider({
privateKeys: [PRIVATE_KEY],
providerOrUrl: RPC_URL,
});
const provider = new ethers.providers.Web3Provider(hdWalletProvider);
const address = await provider.getSigner().getAddress();
const seaport = new Seaport(provider, {
conduitKeyToConduit: CONDUIT_KEYS_TO_CONDUIT,
});
const { executeAllActions } = await seaport.createOrder(
{
offer: [
{
itemType: ItemType.ERC721,
token: "0xa499f7d121dd0daa7e65b273a773cdca45a68cb0",
identifier: "0",
},
],
consideration: [
{
amount: ethers.utils.parseEther("0.000001").toString(),
recipient: address,
},
],
conduitKey: CROSS_CHAIN_DEFAULT_CONDUIT_KEY,
},
address
);
const order = await executeAllActions();
console.log("order", order);
const orderV2 = await api.postOrder(order, {
protocol: "seaport",
side: "ask",
});
console.log("orderV2", orderV2);
}
main();
process.on("unhandledRejection", (reason) => {
console.error(reason);
process.exit(1);
});
修正したけどエラーがでた
Error: API Error 400: You provided an invalid zone: 0x0000000000000000000000000000000000000000, please use OpenSea's zone: 0x004c00500000ad104d7dbd00e3ae0a5c00560c00 for order placement
zone、とは...?

PausableZone ってやつらしい
必要なインタフェースは次の通り
validator を横から差し込める君っぽい
PausableZone は緊急時にマーケットの取引を止める君っぽい?
pause
すると selfdestruct
する仕組みらしく、pause 後は↓の判定が false になるって感じかな

import { Seaport } from "@opensea/seaport-js";
import { ItemType } from "@opensea/seaport-js/lib/constants";
import { ethers } from "ethers";
import HDWalletProvider from "@truffle/hdwallet-provider";
import { OpenSeaAPI } from "opensea-js";
import {
CONDUIT_KEYS_TO_CONDUIT,
CROSS_CHAIN_DEFAULT_CONDUIT_KEY,
DEFAULT_ZONE_BY_NETWORK,
} from "../node_modules/opensea-js/lib/constants";
import { Network } from "../node_modules/opensea-js/lib/types";
const RPC_URL = process.env["RPC_URL"]!;
const PRIVATE_KEY = process.env["PRIVATE_KEY"]!;
async function main(): Promise<void> {
const api = new OpenSeaAPI({});
const hdWalletProvider = new HDWalletProvider({
privateKeys: [PRIVATE_KEY],
providerOrUrl: RPC_URL,
});
const provider = new ethers.providers.Web3Provider(hdWalletProvider);
const address = await provider.getSigner().getAddress();
const seaport = new Seaport(provider, {
conduitKeyToConduit: CONDUIT_KEYS_TO_CONDUIT,
});
const { executeAllActions } = await seaport.createOrder(
{
offer: [
{
itemType: ItemType.ERC721,
token: "0xa499f7d121dd0daa7e65b273a773cdca45a68cb0",
identifier: "0",
},
],
consideration: [
{
amount: ethers.utils.parseEther("0.000001").toString(),
recipient: address,
},
],
conduitKey: CROSS_CHAIN_DEFAULT_CONDUIT_KEY,
zone: DEFAULT_ZONE_BY_NETWORK[Network.Rinkeby],
},
address
);
const order = await executeAllActions();
console.log("order", order);
const orderV2 = await api.postOrder(order, {
protocol: "seaport",
side: "ask",
});
console.log("orderV2", orderV2);
}
main();
process.on("unhandledRejection", (reason) => {
console.error(reason);
process.exit(1);
});
変更したけどエラーが出た
Error: API Error 400: You provided an invalid order type: OrderType.FULL_OPEN, please set it to be FULL_RESTRICTED or PARTIAL_RESTRICTED
パラメータが変なのかな

createOrder
の consideration
を理解した
リスティングするときの費用や手数料を設定するところらしい
opensea-js
のデフォルトはこんな感じ

createOrder
に
restrictedByZone: true,
allowPartialFills: true,
を指定すると別のエラーが出た
Error: API Error 400: You have attempted to create a listing, but you provided an invalid number of consideration items: 1. A consideration array for a listing may only have 2 or 3 items

OpenSea 側が取る手数料は 250 (多分パーミル)

import { Seaport } from "@opensea/seaport-js";
import { ItemType } from "@opensea/seaport-js/lib/constants";
import { ethers } from "ethers";
import HDWalletProvider from "@truffle/hdwallet-provider";
import { OpenSeaAPI } from "opensea-js";
import {
CONDUIT_KEYS_TO_CONDUIT,
CROSS_CHAIN_DEFAULT_CONDUIT_KEY,
DEFAULT_ZONE_BY_NETWORK,
} from "../node_modules/opensea-js/lib/constants";
import { Network } from "../node_modules/opensea-js/lib/types";
const RPC_URL = process.env["RPC_URL"]!;
const PRIVATE_KEY = process.env["PRIVATE_KEY"]!;
async function main(): Promise<void> {
const api = new OpenSeaAPI({ networkName: Network.Rinkeby });
const hdWalletProvider = new HDWalletProvider({
privateKeys: [PRIVATE_KEY],
providerOrUrl: RPC_URL,
});
const provider = new ethers.providers.Web3Provider(hdWalletProvider);
const address = await provider.getSigner().getAddress();
const seaport = new Seaport(provider, {
conduitKeyToConduit: CONDUIT_KEYS_TO_CONDUIT,
});
const { executeAllActions } = await seaport.createOrder(
{
offer: [
{
itemType: ItemType.ERC721,
token: "0xa499f7d121dd0daa7e65b273a773cdca45a68cb0",
identifier: "0",
amount: "1",
},
],
consideration: [
{
amount: ethers.utils.parseEther("0.000001").toString(),
recipient: address,
},
{
amount: ethers.utils.parseEther("0.000001").toString(),
recipient: address,
},
],
endTime: Math.ceil(
(new Date().getTime() + 10 * 24 * 60 * 60 * 1000) / 1000
).toString(),
conduitKey: CROSS_CHAIN_DEFAULT_CONDUIT_KEY,
zone: DEFAULT_ZONE_BY_NETWORK[Network.Rinkeby],
restrictedByZone: true,
allowPartialFills: true,
},
address
);
const order = await executeAllActions();
console.log("order", order);
const orderV2 = await api.postOrder(order, {
protocol: "seaport",
side: "ask",
});
console.log("orderV2", orderV2);
}
main();
process.on("unhandledRejection", (reason) => {
console.error(reason);
process.exit(1);
});
適当に指定すると怒られる
Error: API Error 400: OpenSea fees are required to post this order.
OpenSea への fee を含めることを必須にしている
二次流通時の還元を必須化するマケプレとかも作れそう

consideration: [
{
amount: amount.toString(),
},
{
amount: amount
.mul(DEFAULT_SELLER_FEE_BASIS_POINTS)
.div(INVERSE_BASIS_POINT)
.toString(),
recipient: OPENSEA_FEE_RECIPIENT,
},
],
consideration
に OPENSEA_FEE_RECIPIENT
を指定しても同じエラーがでる
なんでやろ...

BasicErc721Item
と Erc721ItemWithCriteria
の違い