見出し画像

SUIチェーンでAtomic Arbを行うまで

本記事はBotter Advent Calendar 2024に投稿する記事です。

こんにちは、STEPNerのShunです。普段は健康ロングをしながら、趣味としてBotter活動にも手を出しています。

Botter活動はあまり発信していないのですが、過去のBotterアドベントカレンダーの内容が大変参考になったので、私もなにかGiveしたいなと思い書いています。(あとBotterオフ会に呼ばれたいです)

コンテンツとしてあまり被らないものがいいだろうなと思い、多くの人が手を出していないだろうSUIチェーンをテーマに記事を書きます。


はじめに

Botter活動にもいくつかのジャンルがあるのですが、私が主に手を出しているのは、アビトラ、その中でも、特にEVM系のチェーンのDEX上でのAtomic Arbになります。

本日のAdvent Calendarの表面を書いている5000e12さんもAtomic Arbをテーマにされています。

アビトラの種類についてはくりぷとべあーさんの記事が大変参考になります。

この記事で書かれている通り、Atomic Arb勝者総取りの領域なので、私は鯨が見つけないような小さい鞘を見つけることで戦っています。(なので大して儲かっていない)

ただ、儲け以上に、ノーリスクかつ少ない原資で戦えること、ブロックチェーンのAtomicitiyを感じられること、なにより、利益を出せるTXが通ったときの達成感が高く、それを楽しみに日々研鑽しています。

Atomic Arbの魅力はmagitoさんの以下の記事に詰まっています。
ちなみにmagitoさんもSUIに参戦されているようなので、SUIでの鞘はすでに刈り取られているのではないか…と不安です。

Atomic Arbで実際にどうやって利益を出すのか?という体験談は以下のheppokoyaroさんの記事が大変参考になります。
どのようなところに鞘が落ちているのか、どうやって鞘を取るのか、といった具体的なイメージが湧きます。

ちなみに私の心の師匠はqashさんで、Atomic Arbを始めるに当たり、特に以下の記事にインスパイアされました。

EVM(UniswapV2/V3)でのAtomic Arbの基本

そもそものAtomic Arbの原理について簡単に説明します。

一つのDEXの中でのAtomic Arb

DEXのAtomic Arbは、複数のプールをまたぐ閉路の取引を行うことで、取引の最初と最後でトークン量が増えているような取引を行うことです。

例えばUSDC→ETH→WBTC→USDCという取引を行い、最終的に出てくるUSDCが増えていれば成功です。

このような取引を、一つのDEXの中で行う場合は、DEXのコントラクトを叩けば実行できるので、比較的容易です。

一つのDEXでAtomic Arbをするイメージ

ただし、同じようなAtomic Arbを、複数のDEXをまたいで行う場合は、追加での開発が必要です。

複数のDEX横断でのAtomic Arb

DEXのコントラクトは基本的に、自分たちの取引プールの取引しか行うことができないため、例えばDEX AでETHを買って、DEX BでETHを売る、というような取引を行うことはできません。

このような場合は、DEX A、DEX Bの両方のメソッドを呼び出せる独自コントラクトをSolidityで開発し、ブロックチェーン上にDeployの上、独自コントラクトを叩くことでようやく取引が実行可能となります。

複数のDEXでAtomic Arbをするイメージ

単にDeployすればいいという話なのですが、面倒です。

余談

ちなみにAtomic Arbの調査をしていたら、Atomic Arbをテーマに卒論を書いている人がいました。

https://pub.tik.ee.ethz.ch/students/2022-FS/BA-2022-03.pdf

奇しくも大学名がETHですが、ETHは欧州で3本の指に入る名門です。アビトラ収益機会を探りながら、学位も取れるのは楽しそうですね。

SUIチェーンの特徴① - オブジェクト中心モデル

SUIはトークン含めあらゆる物がオブジェクトとして管理されています。EVMでは、自分のトークンはあくまでトークンのコントラクト上の数字であったものですが、SUI上のトークンは、トークンの型(Type)であるオブジェクトとして、各アドレスが保有します。

これによる具体的なメリットについてはあまり詳しくないのですが、ガス代が節約されたり、コードが簡素化されるため、開発者がバグのない開発をしやすい、というメリットが有るようです。

確かにコードを書いているうえでも、かなりシンプルに書けるなという印象です。

ちなみにMove系は全般的にオブジェクト中心モデルのようですので、Aptosでも同様なメリットはあるようです。(とはいえSuiとAptosのオブジェクトの構造も違うそうですが)

SUIチェーンの特徴② - PTB

SUI固有の特徴としてあげられるのは、複数の呼び出し処理を、「Programmable Transaction Blocks」というものにしてまとめて送ることができるということです。
このPTBで送った呼び出し処理は順番に実行され、途中でエラーが発生するとPTB全体がまとめてRevertするような形となっています。

MystenのPTBの作り方の例をそのまま参照しますが、例えば以下のような使い方になります。
(ちなみにドキュメントの更新が追いついていないためか、最新のパッケージでこのコードをそのまま実行しても動かなかった記憶があります。)

//Javascript
import { Transaction } from '@mysten/sui/transactions';

// ここでPTBの箱を作る
const tx = new Transaction();

// PTBで実行する1つ目のTXとして、自分が持っているコインのObjectをSplitする。ここでは、100にSplitされたCoinのObjectが戻り値として返ってくる。
const [coin] = tx.splitCoins(tx.gas, [100]);

// PTBで実行する2つ目のTXとして、Coinを送信する。ここで、1つ目の戻り値のObjectを、そのまま次のTXの引数として利用している。
tx.transferObjects([coin], '0xSomeSuiAddress');

// 2つのTXが入ったPTBををまとめてサインして実行
client.signAndExecuteTransaction({ signer: keypair, transaction: tx });

このPTBは、例えばLPステーキングの際にはTokenAをApproveして、TokenBをApproveして、Depositして、Stakeして…と複数回のポチポチが必要だったやり取りを、ひとつのポチで済ませることができます。

実際に使われている例として、以下のような取引が上げられます。
Naviというレンディングプロトコルで、獲得したリワードを再度貸し出する、という取引になるのですが、いろいろなところからトークンをクレームし、再度預けるまでの処理を一つのTXにまとめて、実行しています。

15もの処理を一つのTXにまとめて呼び出し

当然、このような処理をするコントラクトを作って、実行させることも可能なのですが、すべてのケースを網羅してコントラクトを作るのも手間であるため、このようにPTBで実行できるのは便利そうですね。

Botter目線からすると、わざわざ個別のコントラクトを作成しなくとも、このLending ProtocolでFlashLoanして、こっちのDEXでSwapして、あっちのDEXでSwapして、FlashLoanを返して…といった処理をまとめて一つのPTBで実行することができます。
新しい機能を実装するときは、TXに処理を追加して投げるようにするだけでOK。(コントラクトを再デプロイしなくていい!)

まさに、Atomic Arbのために作られたと言っても過言ではない機能ですね。

PTBを使ったAtomic Arbのイメージ

実際にやってみた

とはいえ結構面倒でした。

  • Documentの更新が間に合っていないためか、Document通りにやっても動かない

  • 取引所ごとにメソッドが異なるため、それぞれのコードを見に行って個別に実装する必要がある

  • 最大のDEXであるCetusがそもそも複数DEXの流動性を使った取引を行っており、市場の歪みが少ない

  • その数少ない収益機会にさえも数人のTXが群がっている状態

EVMからきた身としては全く違う市場状況にかなり驚きました。そして、まだSUIには誰も来ていないから濡れ手で粟だろう、という夢は粉々に打ち砕かれました。

結果

本来なら、Swap XX SUI for YY USDCなどと表示される。
今回は入金と出金が相殺されており、入金0に対して 0.000068 USDCが出金されてる。

虚無からUSDCが生まれている!
ただ、今回はガス代を考慮していないので、しっかりと赤字になっています。(逆に言うと赤字だから誰も取らなかった)

本当にAtomic Arbで勝つためには、ガス代との兼ね合いを考えたうえでTXを通すか否かを考えたり、自分が持っていないトークンでも収益を得られる様に、FlashLoanでお金を借りてきたり、更にはTXを早く検知して通すためにNodeを立てたり…といった改善が必要だと思われます。(私にはまだ力不足です)

また、技術面以外にも、そもそもの収益機会を見つけることが大変です。SUIのエコシステムに詳しい方いたら教えて下さい。

おまけ)SUIチェーンでAtomic Arbをするコード

実際のコードのイメージは以下のような形になります。(このままでは動きませんし、このコードをベースにした取引での損失による責任も負いません。あくまでDYORでお願いします。)

//Javascript
//初期設定、Mystenが便利そう
import { Transaction } from "@mysten/sui/transactions";
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';

const keypair = Ed25519Keypair.fromSecretKey(PRIVATE_KEY_SUI); // 秘密鍵は自分で用意
const client = new SuiClient({
    url: 'https://fullnode.mainnet.sui.io:443',
});

// 裁定ルートの設定
const path = [hop1, hop2, hop3];

// 最初のTXの送金額と、最低限帰ってきてほしいOutputを設定
const amountIn = hoge;
const minAmountOut = piyo;

// 新しいPTBを作成して初期設定をする
const tx = new Transaction();
tx.setSender(keypair.getPublicKey().toSuiAddress());

// PTBのTXその① 送るトークンのオブジェクトを作成(今回はtx.gas = SUIトークンを利用)
[coin] = tx.splitCoins(tx.gas, [tx.pure.u64(amountIn)]);

// PTBのTXその② 各裁定ルートのHOP事に取引を実施。
for (const hop of path) {
    coin = buildSwapTxForFugaDex(tx, hop, coin);
    }

// 取引所のTXを作るヘルパー関数(DEX毎に仕様が違うので、Documentを読んで頑張って実装する。)
function buildSwapTxForFugaDex(tx, hop, coin) {
    const tokenInType = hop[tokenIn];
    const tokenOutType = hop[tokenOut];
    const poolObject = hop[poolObject];
    const targetFunction = "0x...::fuga::swap";
    const [output] = tx.moveCall({
        target: targetFunction,
        typeArguments: [
            tokenInType,
            tokenOutType,
        ],
        arguments: [
            tx.object(poolObject),
            coin,
        ],
    });
    return output;
}

// PTBのTXその③ 帰ってきたお金が最低限のOutputを満たしているか確認(Cetusのメソッドを使うのが便利そう)
const cetusObject = "0x11451575c775a3e633437b827ecbc1eb51a5964b0302210b28f5b89880be21a2";
tx.moveCall({
    target: `${cetusObject}::utils::check_coin_threshold`,
    typeArguments: [
        0x2::sui::SUI
    ],
    arguments: [
        coin,
        tx.pure.u64(minAmountOut),
    ]
});

// PTBのTXその④ 帰ってきたコインを自分のアドレスに送付(Cetusのメソッドを使うのが便利そう)
tx.moveCall({
    target: `${cetusObject}::utils::transfer_or_destroy_coin`,
    typeArguments: [
        0x2::sui::SUI
    ],
    arguments: [
        coin
    ]
});

// TXをビルドして、署名して、送信!
const txBytes = await tx.build({ client });
const { signature } = await keypair.signTransaction(txBytes);
const result = await client.executeTransactionBlock({ transactionBlock: txBytes, signature: signature });

console.log(result);

ちなみに私自身は理系出身ですが、コーディング経験は殆どなく、大半はCharGPTに書いてもらいました。便利な世の中になりましたね。

いいなと思ったら応援しよう!