
さあfxhashをはじめよう
はじめに
こんにちは。kusakariです。私は普段p5.jsを使ってジェネラティブアート作品を作り、OpenProcessingやTwitter上で公開しています。
今回fxhashでNFT作品を発表したのですが、その過程でいくつかつまずきがあり、かつ日本語情報が全然なくて非常に困ったので、ここに「fxhashでNFT作品を発表するにはどうしたらよいのか」を置いておきます。
また昨今日本のクリエイティブコーダーの作品を盗用してfxhashなどに出品するcopy-mintingが多く見られており、だったらもっと日本のクリエイター達が盗まれる前にどんどんfxhashに出品できれば、もっと報われるのでは、と思ったことも発端です。
ちなみにfxhashで今回公開した作品はこちらです。
必要なSTEP
仮想通貨Tezosを準備する
ウォレットを作る
fxhashにウォレットを紐づけてアカウントを作成する
Tezos Profilesに登録してfxhashにプロフィールを紐づける
作品(Generative Token)を作る
fxhashのsadnboxでテストする
fxhashでGenerative Tokenをmintする
ところでfxhashって何?
fxhashとはブラウザ上で動く形式のNFT作品を出品し、売り買いできる場所です。さらに1次売買だけでなく、購入したNFTを出品し、2次売買も可能です。
fxhashではランダム性を持つGenerative Token(GT)と呼ばれる作品をクリエイターが出品し、購入者がGenerative Tokenをmintすると、mint毎にGenerative Tokenのランダム値が固定化され、一意性の表現を持つNFTとして所有(なのだっけ、法的には違うんだっけ)されることになります。クリエイターはmintされる数(edition)と価格を決定できます。いわゆるArt BlocksのTezos版です。各購入者はmintするまでどういう生成物になるかわからない、というのがまさにジェネラティブでおもしろい点で、Foundationなど画像や動画のNFTプラットフォームとも異なる点です。
さあはじめよう
1. 仮想通貨Tezosを準備する
fxhashでは仮想通貨Tezos(単位: XTZ, tezとか表記される)を用いてNFTを売買します。NFTを出品するにはGas代がかかるので、出品だけでもTezosを準備する必要があります。とはいえfxhashやHic et NuncなどTezosを使うNFTプラットフォームは、FoundationやOpenSeaなどETHを使うプラットフォームと比べ、Gas代が安いです。それが気軽な出品を可能にし、fxhashやHic et NuncなどTezos圏でGenerative artシーンが活況を呈している一因なのかなと思います。
Tezosは適当な仮想通貨取引所を使って購入します。好きなところでやればよいですが、2021年12月時点で日本では2社のみ取り扱っているらしい。私はGMOコインを使いました。使い方は適当に調べてくださいませ。1 tez買っておけば十分です。
2. ウォレットを作る
Tezosを格納するお財布・ウォレットを作成します。ウォレットが自分が自分であることを証明し、それをfxhashに紐づけてアカウントを作成できます。
fxhashのabout > guide to collectを見るとウォレットとしてTempleまたはKukaiをお勧めしています。TempleはMetamaskのようなブラウザ拡張機能で、Kukaiはウェブサイトです。私はKukaiにしました。インストール不要で楽そうなのと、モバイルからでも簡単に使えるので。

Kukaiのウェブサイトを開きます。

ここではTwitterアカウントと連携させてウォレットを作成するので、Sign in with socialの下にあるTwitterをクリックし、自分のTwitterアカウントで認証します。

これでKukaiのウォレット作成は完了です。下記の画面のようにKukaiのウェブサイトを開くと自分のウォレットの内容が表示されます。以降ウォレットの認証を求められる度にこのKukaiのウェブサイトを開いて認証していくことになります。
fxhash上で出品したりプロフィールを変えたりするのにも少しGas代がかかるのでウォレットにTezosを少し入れておく必要があります。仮想通貨取引所で購入したTezosをこのKukaiウォレットに移動しておきます。Kukaiのウォレットを開いた状態で右上の□□をクリックするとこのウォレットのアドレスをコピーできるのでそれを用いてこのウォレットにTezosを移動します。1 tezあれば十分です。

3. fxhashにウォレットを紐づけてアカウントを作成する
fxhashに行きます。

右上のsyncをクリックするとどのウォレットに紐づけるか選択する表示されるのでKukai Walletをクリックします。

Kukaiのウェブサイトに移りfxhashへの承認を求められるのでAPPROVEをクリックして承認します。

fxhashに戻ると自分のウォレットに紐づけられたアカウントが作成されていることがわかります。※この画像ではすでにアイコンが設定されていますが、これは自分でアイコン画像を設定しない限り真っ黒の初期状態になっています。

4. Tezos Profilesに登録してfxhashにプロフィールを紐づける
fxhash上にプロフィールを作っていきます。特にTwitterがfxhashアカウントに連携されていないと偽物と疑われてしまうので、必ずfxhashプロフィールとTwitterを連携させることをお勧めします。
fxhashプロフィールにtwitter連携させるにはtzprofiles(Tezos Profiles)を使用します。手順としては、① fxhashのプロフィール画面でアイコン画像、名前、説明を設定する、② Tezos Profilesを使ってtwitterと紐づける になります。tzprofilesにもアイコンや名前、説明を記入するのですが、それがfxhashにも反映されるかはわからん。先にfxhashの方で設定したらそうなったので。
① fxhashプロフィール画面で設定する
fxhashの右上のアイコン横のドロップダウンリストからedit profileを選択し、プロフィール設定画面に行きます。

アイコン画像、Name、Descriptionを設定・記入します。Submitを押すとKukaiで承認が求められるので承認します。この時ほんの少しTezosを消費します(0.000x tezとかそんなもんだった気がする)。そのためTezosを少額持っておく必要があります。適用までに1分もかからないくらい時間がかかります。KukaiのRecent Activityの一番上に「今処理中だよ~」みたいな表示から「完了!」みたいな表示になったら、fxhashのアイコン、名前、説明への適用が完了します。
ただしこれだけだと偽物でもできてしまうので、プロフィールをTwitterと連携させる必要があります。下記は連携済みですが、未連携だと「tzprofilesに連携させるプロフィールがないよ」みたいなメッセージが表示されています。

② Tezos Profilesを使ってtwitterと紐づける
プロフィールページ下の方のtzprofilesをクリックしてTezos Profilesのウェブサイトに移ります。
ページ下部のConnect Walletをクリックします。

Choose network and connectのmainnetが選ばれているのでそのままConnect Walletをクリックします。(正直mainnetとかよくわからない)

Kukai Walletをクリックします。

Kukaiで承認が求められるのでAPPROVEで承認します。

Tezos ProfilesにKukaiのウォレットが紐づけられ、My Credentialsに自分が設定している情報一覧が表示されます。下記はBasic Profile InformationとTwitter Account Verificationの設定が完了しているのでStatusがCompleteになっていますが、初期では全てIncompleteになっています。
ここでBasic Profile InformationとTwitter Account Verificationを設定します。

Basic Profile Informationの右端のAction列のVerifyをクリックし、Alias(名前)、Description(説明)、Website、Logoを設定します。Websiteはfxhashのプロフィールに紐づけられます。他は関係ないかも。Logoはアドレスで指定するのですが私はTwitterアイコンのアドレス https://twitter.com/(TwitterのID)/photo を使ったらうまくいきました。Dropboxに置いた画像のリンクだとうまくいかなかった。最後にSubmit的なものをクリックして、Kukaiで承認したらOK(もう設定しちゃったので再現画像撮れず)。そのうちtzprofilesのMy CredentialsのBasic Profile InformationのStatusがCompleteになります。
次にTwitter Account Verificationをします。右端のVerifyをクリックして、Twitter IDを記入、その後「この内容でツイートしてね」という項目があるので、そのままツイートボタンを押してツイートし、そのツイートのリンクを貼り付けて、Submit的なものをクリックします。ツイート内容は下記です。このツイートは一応消さないようがいいと思います。
I am attesting that this twitter handle @kusakarism is linked to the Tezos account tz2PpevZBio47EWzoyLBHpvK6JbG9pyoZiLn for @tzprofiles
— kusakari (@kusakarism) December 26, 2021
sig:spsig16nrakkXkTcUACDCsRfb17mCGQktJy7wWyhgNBXVbrvT5JzqF5bPMGrh5dSwaEMkfKkEktnx1PZPdHmJHXeNG6YYwJNUJY
そのうちtzprofilesのMy CredentialsのTwitter Account VerificationのStatusがCompleteになります。
これでTezos Profilesの設定は完了ですが、要注意ポイントとしてfxhashのプロフィールにTwitterとWebsiteが連携されて表示されるまでに数時間かかりますので、不安になっても気長に待つしかありません。(私は焦って一度Tezos Profilesの設定を削除し、再度認証したりしてしまいました)
数時間待ってfxhashのプロフィールのページにTwitterとWebsiteが下記のように表示されていればOKです。

5. 作品(Generative Token)を作る
さて、準備が整いました。あとは作った作品をfxhash上にアップロードしていきます。思い思いに作品を作りましょう。(パクリ、ダメ、絶対)
詳細はfxhash > about > Guide to mint a Generative Tokenに書いてありますので私なりに気を付けるべきと思った点を説明します。
fxhash上に出品する作品は下記の形をとります。
index.html
jsファイル
cssファイル
その他画像ファイルなど
fxhash上の作品はこのindex.htmlを実行するだけなので、それに紐づけられたjsファイル、cssファイル、画像ファイルなどで実現できればp5.jsでもthree.jsでもなんでもいいはずです。(three.jsのこと全く知らない)
なお、外部のファイルやライブラリにアクセスは禁止で、あくまでこのパッケージ内で完結するようにしなければなりません。p5.jsライブラリや画像など全部ここに入れておきます。これを1つのzipファイルにまとめてfxhashにアップロードします。またzipファイルは30MB以内でなければなりません。
典型的なp5.jsを用いた作品では下記で良いと思います。
index.html
p5.js
sketch.js
CSSで特別なことをしないのならhtmlにstyleタグで書き込み、cssファイル不要、というスタイルです。別にcssファイルを使ってもいいし、お好きにどうぞ。
1. index.htm
最もfxhashで特徴的な部分として、index.htmlにscriptタグで囲まれた下記の部分を必ずheadタグ内に入れなければならない、というものがあります。またその文言は編集も不可です。fxhash上のジェネラティブ作品内ではrandom()関数の代わりに使われるfxrand()関数や、fxhashという変数を使うことができます。これらはNFTが購入された(mintされた)時に、固有の値として付与され、それによりそのNFTは固有な表現として出力されます(つまりランダムな値が固定化される)。下記scriptタグ内の文言はそれらのfxhash変数、fxrand()関数を機能させるため、最終的にそれらに固有の値を与えるためのものです。
headタグ内のどこかにcssの代わりにstyleタグで情報を書き込みます。画面の余白をなくすためにbody{}内に(表示される全領域を対象に)padding: 0; margin: 0; を記入します。さらにサイドバーを非表示にするためにoverflow: hidden; を記入します。(私は不勉強でoverflow: hidden;を書いてなくてサイドバーが表示されてしまいました…。windowWidth, windowHeightで画面ピッタリのcanvasサイズなのにサイドバー表示されてしまうのなんでだろう)
p5.jsライブラリにアクセスするための、<script src="./p5.js"></script>と作品のコードを書いた本体sketch.jsにアクセスするための<script src="./sketch.js"></script>を記入します。
titleタグ内には作品の名前などちゃんと意図するものを記入してください。これは作品をブラウザで単体表示した時にブラウザのタブに表示されます。(作品の一部として表に出る部分です)
<!DOCTYPE html>
<html>
<head>
<title>FXHASH project</title>
<script id="fxhash-snippet">
//---- do not edit the following code (you can indent as you wish)
let alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
var fxhash = "oo" + Array(49).fill(0).map(_=>alphabet[(Math.random()*alphabet.length)|0]).join('')
let b58dec = str=>[...str].reduce((p,c)=>p*alphabet.length+alphabet.indexOf(c)|0, 0)
let fxhashTrunc = fxhash.slice(2)
let regex = new RegExp(".{" + ((fxhash.length/4)|0) + "}", 'g')
let hashes = fxhashTrunc.match(regex).map(h => b58dec(h))
let sfc32 = (a, b, c, d) => {
return () => {
a |= 0; b |= 0; c |= 0; d |= 0
var t = (a + b | 0) + d | 0
d = d + 1 | 0
a = b ^ b >>> 9
b = c + (c << 3) | 0
c = c << 21 | c >>> 11
c = c + t | 0
return (t >>> 0) / 4294967296
}
}
var fxrand = sfc32(...hashes)
// call this method to trigger the preview
function fxpreview() {
console.log("fxhash: TRIGGER PREVIEW")
}
//---- /do not edit the following code
</script>
<!-- if you need to import js scripts do it here -->
<style>
body {
padding: 0;
margin: 0;
overflow: hidden;
}
</style>
<script src="./p5.js"></script>
<script src="./sketch.js"></script>
</head>
<body>
</body>
</html>
2. p5.js
p5.jsライブラリも外部参照でなくfxhashにアップロードするzipファイルに含めておきます。(外部参照すな、とfxhashの手引きにもあり)適当に最新のものをindex.htlmlと同じ階層に入れておきます。
3. sketch.js
まさに作品の本体のなるファイルで、p5.jsなどを使用した作品のコードをいつものように書き込みます。注意点は2つ、① mint後のランダム性をなくす、② windowサイズ変更に対応する です。
3-① mint後のランダム性をなくす
fxhash作品を購入者がmintした後、生成されたNFTはランダム性を持たない、一意性のある表現である必要があります。コード内にrandom()があると実行の度に結果が変わってしまいます。ランダム値をmintごとに固有の固定値にするために、fxhashではランダム値生成関数としてfxrand()関数を使用します。fxrand()は0~1の間のランダム値を返すので、実際にはコード内のrandom()を全てfxrand()に差し替えればOKです。
ただしrandom(1, 10)のように最大値最小値がある場合は、
最小値 + fxrand() * (最大値 – 最小値) のようにする必要があります。
もしくは最初にコードの最初にrandomSeed()関数を使い、そのseedにfxrand()やfxhash変数を用いて、random()関数をmint後に固定化してもよいです。fxhash変数は、mint毎に固有の1つの51の文字列を返す、というものです。私は使いませんでした。
見逃しやすい点としてnoise()関数も実行ごとに異なる値を返すので、noiseSeed()関数にfxrand()関数などを用いてmint後の固定化をする必要があります。
3-② windowサイズ変更に対応する
fxhashの手引きを見ると、あらゆる画面サイズに対応し、かつ画面サイズの変更されたときにも反映するのがよい、とあります。
私のおススメは下記の3つを入れ込むことです。
canvasサイズをwindowのサイズにする
使用する長さの基準をwindowサイズを基準にする
windowResized()でwindowサイズ変更を反映する
3-②-1. canvasサイズをwindowのサイズにする
下記のようにcanvasサイズを絶対値でなくwindowサイズに合わせるように設定します。これでfxhashのプレビューの□のサイズにも設定されます。
createCanvas(windowWidth, windowHeight);
3-②-2. 使用する長さの基準をwindowサイズを基準にする
canvasサイズを可変にしても、コード内で使用するオブジェクトの大きさや線幅などが絶対値ではwindowサイズの変更に追従できないので、canvasサイズに合わせて調整されるように設定します。
そのために、グローバル変数として基準長さを宣言して、基準長さをmin(width, height)と画面縦横の短い方に設定し、コード内の大きさに関わるあらゆる変数を 基準長さ×相対値 で表現する、というものです。位置、サイズ、線幅など全部です。画面の縦横どちらかとか、大きい方に合わせるとか意図的に設定してもよいです。
具体例としては下記のようにします。
let _minWidth;
function setup() {
...
_minWidth = min(width, height);
strokeWeight(_minWidth/400);
...
}
3-②-3. windowResized()でwindowサイズ変更を反映する
画面のサイズをコード実行中に変更された際に、変更後のサイズに合わせて全ての表現の縮尺も調整されるようにwindowResized()関数を利用します。
windowサイズが変わるとwindowResized()関数が実行されるので、その中に上記3-②-1のcanvasサイズをwindowサイズにする、
createCanvas(windowWidth, windowHeight);
及び上記3-②-2の基準長さをwindowサイズにする
_minWidth = min(width, height);
を含めたサイズが関わる全てを実行しなおします。実際的にはsetup関数内のほぼ全部をやり直せばよいと思います。(windowサイズが変わるたびに最初から実行し直されるイメージ)
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
_minWidth = min(width, height);
...
setup()内やり直し
...
}
windowResized()でwindowサイズ変更する注意点として、draw()関数内でbackground()を使わず描画の軌跡を残し続ける作品の場合、後述するサムネイル用静止画取得がうまくいかないようです。そのようなdraw()関数内の描画を重ね続ける作品の場合、この3-②-3のwindowResized()の項目は省略する必要があります。
index.html, p5.js, sketch.jsが揃ったらそれらを1つのzipファイルにします。zipファイルの最初の階層にそれらのファイルが入るようにします。3つのファイルをまとめたフォルダをzipファイルにするとzipファイル内にまとめフォルダがありその下に3つのファイルが配置されてしまうので、注意してください。ちゃんというと、index.htmlファイルがzipファイル直下にあれば、他はhtml内で相対パスが正確に記述されていれば、階層が異なっていてもOKです。
6. fxhashのsandboxでテストする
作品を作ってzipファイルにまとめたらGenerative Tokenとしてmintする前に、fxhash上で正しく動作するかをfxhashのsandboxでテストします。fxhashの右上のsandboxをクリックして、sandboxに移ります。
左にある黒□にzipファイルをドラッグアンドドロップするか、黒□をクリックして、zipファイルを選択します。下部のstart testsをクリックすると、実行されます。

右の□に実行結果が表示されるのでまず正しく動作しているか、動作速度は問題ないかを確認します。何も表示されない場合は問題ありです。例えばzipファイルの一番上のルートの階層にindex.htmlがあるか確認してください。(私、これで一回引っ掛かりました)
Current hashというのは、購入者がmintするとそのNFTに固有に付与される文字列で(これがfxhash変数)、それに紐づいてfxrand()関数の値が与えられます。同じhashが付与される限り、同じ結果が返されるはずです。
new hashをクリックして、異なるhashが付与された場合に異なる結果が返されるか確認してください。同じ結果ならジェネラティブじゃなくなってるのでsketch.jsの内容を確認してください(これはあんまりないと思うけど)。
次にretry with same hashをクリックして、同じhashを与えた場合に同じ結果が生成されるか確認してください。異なる結果が帰ってきた場合、購入者がmint後のNFTにランダム性が残ってしまうので、sketch.jsの内容を確認してください。ありえそうなのが、random()関数が残ってしまっている(→fxrand()関数に置換してください)、noiseSeed()関数を使わずnoise()関数を使っている(→noiseSeed()関数を使い、seedにfxrand()関数とか固定値でも使ってnoise()関数の値を固定してください)、などがあります。私はnoiseSeed()関数使ってないところで引っ掛かりました。
右下のopen liveをクリックすると別タブでこの作品単体が(index.htmlが)開きます。全画面で問題ないか、windowサイズを変えても追従するか、など確認してください。
ちなみに私はこのsandboxテストでサイドバー出てなかったので安心してGenerative Tokenをmintしたら、出品された作品ではサイドバー表示になってしまっていたので、index.htmlのstyleにoverflow: hidden;は必要だな、と感じ入った次第です。

7. fxhashでGenerative Tokenをmintする
さあ、最後だ。あとは作品となるGenerative Tokenのzipファイルをmintするだけだ。もう一息だ。がんばろう。
fxhashは一日のうち限られた時間のみGenerative Tokenをmintできます。その時間は1時間ずつ毎日シフトします。ですので、mint可能な時(右上の○が緑色の時)にやりましょう。
この時点で、NFTとしてmintされる数(edition)と価格を決めておいてください。ざっと見た感じ私見ですが、そこそこ名があれば2 tezで割とリーズナブル、5 tezだと攻めてるな、1 tezだと安いかな、という感じ。有名人は10 tezとか20 tezとか指数関数的に上がっている。もちろん知名度だけでなく作品が良ければそれに見合った対価が支払われると思います。見ててめっちゃ良いのに全然安いなって思うこともあります。editionは128とか256とかコンピュータな数字が好きな人多い。2桁は少ない方に感じる。バリエーション少なかったり、お試しで小規模にやるならよさそう。500だと結構多いな、という感じ。売り切れないかも(名がある人ならいいと思う)。大体とりあえず128か少し多めに256ってところか。私は2 tez, 300 editionで出して5日間で196個がmintされました。めちゃくちゃ熱心にtwitterでPRするほうではないと思う。日本でこういう感じの人で、こんな感じの作品を出すと、これくらいなんだという参考にしてください。あとmintされず残ったeditionをburnしてなくすことはできないみたいです。(もしくは自分で買って回収するとか?)2022年1月の変更で、売れ残ったeditionをburnで処分できるようになりました。
さて、アイコン右のドロップダウンリストからmint a Generative Tokenをクリックしてください。下部の「zip確認したよ、mintするよ」みたいなところをクリックしてください。

黒□にzipファイルをドラッグアンドドロップして、uploadをクリックしてください。

ここでも動作をsandboxと同じやり方で最終確認します。

動作問題なければ、作品の顔となるプレビューのhashを選びます。良い生成結果が出るまでnew hashを押し続けてください。そのhashを用いた結果が作品のページで表示されます(重要)。満足が行ったら下部2つのチェック欄両方にチェックしてNext Stepをクリックします。

次に購入者のNFTのプレビュー静止画(一覧としてずらーっとmintされた作品が並ぶやつ)を撮るタイミングを指定します。

Triggerで静止画を撮るトリガーを指定します。トリガーは2つ、Fixed delayかProgrammatic trigger using fxpreview()です。Fixed delayはその名の通り、一定時間の秒数が経ったらその時点の静止画を取得します。fxpreview()はsketch.jsのコード内でfxpreview()が実行された時点で静止画を取得しますので、あらかじめfxpreview()をコード内に埋め込んでおく必要があります。私はFixed delayで7.5秒に設定しました。Fixed delayを選択すると出てくる時間バーで秒数を指定してください。

次にTargetで静止画を撮る領域を指定します。From <canvas>かViewport capture の2つから選択します。From <canvas>はcssで指定したcanvasの領域の静止画を取得します。Viewport captureは生成された画面全体の静止画を指定します。通常Viewport captureでOKです。Viewport captureを選択するとCapture resolutionで画面サイズを指定できます。私はそのまま800*800にしました。

test captureを押すと指定したタイミングでの静止画を取得をテストします。所望の静止画になっていればNext stepで次に進みます。

作品のブラウザ上で動く状態と静止画の状態を最終確認します。

問題なければNext stepで進みます。

次にExplore variations settingsで、Generative Tokenのプレビューページで、閲覧者がどのくらい新しいhashを与えて異なるバリエーションを見ることができるかを設定します。閲覧者が購入前にどういう生成の幅があるのか試すことができるようにするものです。これは新しい機能でたぶん私が出品したときにはなかった機能だと思います。
Pre-mint項目はGenerative Tokenの全てのeditonが売り切れるまで有効な設定で、Post-mint項目は全て売り切れた後に有効になる設定です。全部の可能性を見せたくない場合はPre-mintで表示hash数を絞って全部売切れたら開放する、みたいな使い方かな。私は最初から無限にhash与えて見せちゃえばいいじゃんって思いますが、好きにすればよいです。
まずexploration enabledにチェックをつけると、閲覧者が異なるhashを与えて色んな生成結果を見ることができるようになります。Preview用に設定したhashしか見せたくない場合はこのチェックを外します。
Number of variationsで与えるhashの数を指定します。Infiniteは無限にhashを与え、Constrained to a list of hashesは今右側に表示されているhashをAdd current hash (on right) to listでリストに加えてそのリスト内のhashのみ与えます。設定したら下部のNext stepで進みます。


最後の設定です。作品の名前、説明、個々のmintされたNFTの説明(任意)、タグ(カンマ区切り)、edition数、すぐ販売を開始するか(Can be collected nowにチェック。普通これ)、価格、ロイヤリティー(2次売買時のクリエイターの取り分)を記入します。すぐ販売を開始するか、価格、ロイヤリティーはGenerative Tokenがmintされた後でもいつでも変更可能です。全部記入したらMint TokenでGenerative Tokenをmintします。これで全て完了です!!!!


終わりに
私から言いたいことは一つだけ、さあfxhashをはじめよう。それぞれの創造性を存分に発揮して、世界をもっと豊かに彩りあるものにしていこう。