Solana関連のよもやま話(2月4日)
まとまった技術記事を書こうとするとハードルが高いものの、日々事件や発見があるので、書いてみます。
こういう話に興味がある方は Orca の Discord の dev 関連チャネルへ! 🏃
日本語コミュニティチャネルもあるので活用ください👍
Solana 関連
ATAのCreateIdempotent モード
少し前にようやく気がついたネタですが、AssociatedTokenProgram に CreateIdempotent という命令があります。
ATA があったら作るし、なかったら何もせずにスルーする命令です。結果的にこの命令のあとには必ず ATA が存在する状態になります。
これが嬉しいのは、ATA を作る必要がある処理を複数同時に実行していくときです。同時に実行する2つのトランザクションの両方で、現在存在しない ATA を必要としている場合、Create で作るとどちらかは失敗してしまいますが、CreateIdempotent だとどちらが先に実行されてもうまくいきます。
Jito Labs の Bundles
まだ試してはいないものの、複数のトランザクションを順番かつ、アトミックに実行してくれるらしいです。
1つのトランザクションではサイズが溢れてしまうがアトミックに実行したいんじゃ、という場合には役立ちそう。
Jito Labs の Discord は強強 Validator が集まっててログみるだけで勉強になりますです。
オレオレ Wormhole トークン
Phantom や Solscan で表示されるトークンの名前は、トークンを作った人なら自由に設定できます。なんら Wormhole を経由したラップトークンでなくても、「(Wormhole)」と名前をつけられます。
そのうえ、パーミッションレスのマーケットでそれらしくプールなどもつくっていたりして、それっぽく見えてしまうものがあります。
本当に Wormhole でブリッジされているトークンなのかチェックするにはこのあたりのリポジトリでチェックするのがよさそうです。
本物の Wormhole が mint authority になる場合は、BCD75RNBHrJJpW4dXVagL5mPjzRLnVZq4YirJdjEYMV7 というアドレスだと思います。(DYOR)
ミント(トークンのおおもと)を作るときも、直接 Token Program を実行したりはせず、Wormhole のプログラムが実行しています。
ラップトークンは流動性がなかったりするので、この流動性の多さは本物だ、という感じがいまいちしないのが難点だなぁと・・・
Phantom がなにをやってるのか調べたい
ウォレットはコードが非公開なことが多いので、なにをやってるのか調査するときよく困ります。Chrome のエクステンションはコードをダウンロードできるものの、当然難読化されていてわかりにくいです。
Google Developer Tools でネットワークのアクティビティをみたり、デバッガで変数を覗いたりできないか・・・と悩んでいたら、できました。
Google Chrome Extensionを作ってみた-その2(デバッグ)- | DevelopersIO (classmethod.jp)
このエクステンションIDをつかって chrome-extension:// のパスでアクセスすると、あのポップアップして表示される Phantom がメインのタブに表示されます。
そうすると、Developer Tools の対象にすることができ、調査がはかどりました。こんな感じで使えました。
にしても一挙手一投足、track という名目でPhantom のサーバに行動が送られてる気がするんだけども気のせいだろうか・・・
lamports が number 型で扱われる罠
開発者がローカルで自分のプログラムやメインネットからコピーしてきたプログラムを実行できる便利な solana-test-validator ですが、罠にはまりました。
エアドロップでいくらでも SOL は取得できるのですが、solana address で取得できるアドレスには初期化時点で 500000000 SOL を付与してくれるようです。メインネットでもお願いできませんかね😎
便利だなーと思っていたんですが、lamports にするとこんな数字です。SOL の量は u64 データ型で管理されているので、特にこの時点では問題ありません。
Solana の web3.js SDK ではアカウントの残高を取得するメソッドが Connection クラスにありますが、戻り値は number 型です。
number 型で正確に表せる整数の範囲は $${2^{53}}$$ あたりまでです。大雑把にいって 15 〜 16 桁です。
はい、「500 000 000 000 000 000」は 18 桁です/(^o^)\
なので、ネットワーク手数料を払ったら残高はこれのはずだ、みたいな assertion が死にました。ネットワーク手数料 (5000) が安すぎて有効桁数なにそれになっています。
残高と手数料の見積を console.log してずっと眺めていたのに、なぜか引いてみるとずれる。気がつくまで「何が起きたのか全くわからない(混乱)」になってました。
テスト時のウォレットは Keypair.generate() して、大元のウォレットから定額 transfer するかほどほどに airdrop したウォレットがよさそうです。
富豪(test-validator内のみ)ゆえの贅沢病か・・・
Whirlpool 固有
プールの流動性簡易チェック
SOL/USDC プールで、現在価格が 25 USDC/SOL のとき、20 USDC/SOL まで下がってもいい前提でいくらぐらいまで SOL をぶん投げられるのか、というような疑問があると思います。
簡易的な方法として swapQuoteWithParams 関数が使えます。
この関数はスワップを一定価格に達した時点で止めるための sqrtPriceLimit というパラメータを受け取ります。ですから、明らかに膨大な入力を、20 USDC/SOL という価格リミットを設定して入力すれば、そこまでで交換できる量を教えてくれるわけです。
流動性のこまかな計算をせずとも、SDK の提供機能でもできますよ、というサンプルでした。
2step swap についてSDKで頑張ってみた話
2つ以上のスワップを組みわせるのは Jupiter の得意技ですが、Orca の SDK でできることをやりたいという開発者のかたもおられます。
2つ以上のスワップを組み合わせる時にやっかいなのは、「前のスワップの出力を次のスワップの入力に全部入れる」ということが意外にも難しいことです。
つまり、1個目のスワップの出力が想定より多いという嬉しい事態が発生したら、それを次のスワップに入力できずに残してしまうし、想定より減ったら2個目のスワップの入力が足りずエラーになってしまいます。
Jupiter はプログラムが出力をチェックして次の入力に流すので、この問題を解決していますが、今回はそれには頼らない方法という縛りです。
SDK でこれを防ぐのが ExactOut スワップです。1個目のスワップは ExactOut とし、2個目のスワップは ExactIn とすることで中間での過不足を防ぎます。
ここで結果より多めだった、少なめだったがどう現れるかというと入力の方に現れてきます。トークン価格が見積もり時より上がれば入力が増えます。トークン価格が見積もり時より下がれば、入力が減ります。
このズレを許容しないとすぐエラーになってしまうトランザクションになるので、入力された量を最大値とし、0.5%程度少ない量で1個目のスワップを見積もります。そしてその出力を2個目のスワップの入力にし、期待される出力は0.5%程度すくない量をしきい値とします。
これにより、1%程度のスリッページでの2stepスワップが構成できます。(できてるよね?汗)
オンチェーンで頑張ればいいじゃないかというのはごもっともです\(^o^)/
Wrapped SOL アカウントの rent 負担をユーザにさせない話
Solana のプログラミングをした人は SOL と SPL-Token が同じように扱えるのはウォレットや dapps が頑張っているおかげだとご承知と思います。
WSOLアカウントをつくる rent はアカウントを削除すれば本人のところに戻るのですが、ネットワーク手数料と同じく rent もショップ側などプラットフォームが負担し、アカウントを削除したときにはプラットフォームに戻ってきてほしいという話がありました。
rent分の SOL を送る
WSOLアカウントを作る
スワップをする
WSOLアカウントを削除する
rent分の SOL を返す
というフラッシュローンのようなことをやってみました。プラットフォーム側と利用者側で相互に署名をするものです。
懸念としては「5. rent分の SOL を返す」を削除されないか、ということになりますが、一方が署名をした段階でトランザクションは改変不可能になります。ですから、プラットフォーム側は上記のトランザクションを構成し、署名したものを利用者におくれば、必ず「5. rent分の SOL を返す」がセットのトランザクションが実行できます。(もしくは全く何も起きない)
トランザクションのフローをみると、綺麗に rent 分の SOL が素通りして戻ってきていることがわかります。
サンプルスクリプトの構成も、利用者とプラットフォームが相互の秘密鍵をしらずに順番に署名していけるよ、という説明を兼ねたものになっています。
その他
javascript の every と some
配列要素のすべてが条件を満たす、1個以上が満たす、が簡単にチェックできた。
javascript で 0 〜 127 までの配列をつくる
new Array(128) で長さ 128 の配列が作れる。
map を使えば new Array(128).map((a, i) => i) でできる・・・できない。
長さ 128 でも sparse な状態なので map でも要素はなにも渡されない。
new Array(128).fill(0).map((a, i) => i) なら OK だった。
yep, yup, yeah が同じだった件、そして nope を使い始める
yep, yup, yap などが書き間違いなのかいまいちわからず、まぁ細かいことはいいんだよ、と思っていましたが、同じだとわかって安心しました。yeah も勢いを感じるものの、やはり同じらしい。
要するに「yes」を「うん」とか「そう!」と言ってただけだった。
そして、「no」ってなんか硬いから使いにくいなぁという Yes マンとしては、「nope」を使っていこうと思ったのでした。
useQuery の cache と stale
キャッシュがあればキャッシュを返すけど、stale 経過してたらクエリーをトリガーして新しい情報が得られたらアップデートする。
キャッシュも古すぎると「一旦返す」が問題になってしまうので、cache 経過したらキャッシュは無効にして必ずクエリーさせる。
わかりやすかった・・・
簡潔でいい感じ。
この理解でいくと、staleTime を経過するまではキャッシュを最新としてクエリを抑制するわけだが、cacheTime がすぎているとそのキャッシュがなくなっていて結局クエリをせざるを得ない。
よって、$${staleTime < cacheTime}$$ にしないと意味がないように思われる。という話をしてくれている人がいて安心した。
ですよね、ですよね。
この記事が気に入ったらサポートをしてみませんか?