Intentベースのガスフリー取引:UniswapXの仕組みとメリット
はじめに
こんにちは、@naizo です。
最近、「Intent」という言葉をよく耳にするようになりました。この「Intent(意図)」ベースの取引は、従来の取引方法を変える可能性を秘めています。そこで今回は、Intent ベースの取引の代表例である UniswapX について調査してみました。
この記事では、UniswapX の基本的な概要から技術的な実装まで、幅広く解説していきます。
UniswapX の概要
UniswapX とは何か?
UniswapX は、新しいパーミッションレス(誰でも参加可能)かつオープンソース(GPL)のオークションベース・ルーティングプロトコルです。従来の AMM(自動マーケットメーカー)や他の流動性ソースを横断して、ユーザーが望む結果(Intent)を満たす最適ルートを探しだし、実行者(Filler)がガス代を負担して取引を成立させる点が特徴です。
主な改善点は以下のとおりです。
-
より良い取引価格:
複数の流動性ソースを集約し、最適な価格を提供 -
ガスフリーの取引:
ユーザーではなく取引実行者(Filler)がガスコストを負担 -
MEV 保護:
フロントランニングなどの悪意ある取引から保護する仕組み -
失敗時のコストなし:
取引が失敗しても手数料は発生しない -
将来の展望:
ガスフリーのクロスチェーン取引にも対応する計画があり、チェーンを越えた取引効率化が期待される
なぜ新しい仕組みが必要か?
従来の暗号資産取引には、以下のような課題がありました。
- ガス代(取引手数料)が高い
- 取引失敗時もガス代がかかる
- 最良の取引価格を見つけるのが難しい
- 複数の取引所やプールを比較する必要がある
UniswapX は、これらの問題を「Intent ベースの仕組み」と「Filler 競争」を導入することで解決を試みています。
「Intent(意図)」ベースとは?
従来の取引との比較
従来の取引(例: Uniswap V2/V3 の直接操作)
「AトークンをBトークンに、プールXで、この価格で交換してほしい」
- 具体的ルートやガス代計算をユーザーが行い、トランザクションを自力で送信
- ガス代をユーザーが負担
Intent ベースの取引(UniswapX)
「AトークンをBトークンに、最低でも●●のレートで交換したい」
- 具体的な実行方法は指定せず、「何をしたいか(最終状態)」だけをオフチェーン署名
- ガス代は Filler が負担し、複数 Filler が競争的に最適化
メリット
- ユーザーは最適ルートを自力で探す必要がない
- 失敗時でもユーザーにガス代コストがかからない
取引の基本的な仕組み
UniswapX は大きく Intent(意図) と Execution(実行) を分離しています。具体的には:
1. ユーザー(Swapper)の役割
- どんなトークンを、どのくらい交換したいか(どの価格帯を希望するか)をオフチェーンで署名
- 「どのプールを使うのか」「ガス代はいくら必要か」といった実際のルート選択は書かない
- 作成したオーダーを UniswapX の公開 API などに送信し、「最終的にこれだけのトークンが欲しい」という意図を示す
2. 実行者(Filler)の役割
- 公開されたオーダーを取得
- そのオーダーを満たす方法(Direct Fill / 複数 DEX アグリゲーターなど)を模索
- 利益が出ると判断したらガス代を負担してオンチェーンで実行
- 成功すれば、差益や Filler 手数料を得る
ポイント:
Swapper はガスを用意しなくても取引可能で、Filler がガス代を負担するメリットを得るために競争的に取引を埋める構造になっている。
具体的な取引フロー
下図に示すように、UniswapX は下記のコンポーネントからなる流れを通じて取引を完了します。
システム構成図
-
Swapper(ユーザー)
- トークンを交換したい人
-
Filler(実行者)
- ユーザーのオーダーを見つけて実行し、利益を得る
-
プロトコルコントラクト
- Permit2:トークン転送許可を管理
- Order Reactor:注文の検証と実行を担当
-
外部コントラクト
- Order Executors:実際の取引を実行するコントラクト
- UniV3/Direct Executor:具体的な取引ロジックを実装
フローの各ステップ
-
Swapper → Permit2
- ユーザーがトークン転送の許可をオフチェーン署名(Permit2)
- この署名で、オーダー条件を満たした場合のみトークンを動かせるようにする
-
Swapper → UniswapX API
- 価格や期限などを指定した「署名付きオーダー」を提出
-
UniswapX API → Filler
- API で公開されたオーダー情報を Filler(Bot)が取得
- 最適な取引機会を探索
-
Filler → Order Reactor → Order Executors
- Filler が
execute(...)
を呼び出し、実行を試みる - Order Reactor が期限や署名を検証
- 実行ロジックを Order Executors が担い、トークン交換を行う
- Filler が
こうして、ユーザーは 署名だけ で取引を完了し、Filler がガス代を負担する仕組みが実現する。
UniswapX の主なメリット
以下の項目で、どのように課題を解決しているかを具体的に解説します。
1. コスト面での改善
-
ガスレス取引の可能性
- Filler がガスを負担し、ユーザーはガスを持っていなくてもスワップが可能
- 失敗時コストも Filler がかぶるため、ユーザーにとってリスクが少ない
-
より良い取引価格
- 複数のプール・DEX から最良レートを探すのは Filler が自主的に行う
- Filler 同士の競合により、スリッページやコスト面が改善される
2. スワップロジックの柔軟性
- ユーザーは「最低限このレートを満たしてほしい」と指定するだけ
- 時間経過でレートを下げるダッチオークション(Dutch Order)や、価格が固定のリミットオーダーなど、多彩な形式に対応
3. 使いやすさと安全性
-
簡単な操作
- 署名だけで OK、細かいルート選択やガスの管理は不要
-
失敗時コストがかからない
- オーダーが埋まらなければ自然に期限切れとなるだけ
-
Permit2 による安全な資産管理
- オフチェーン署名で許可されないとトークンは動かない
4. 従来の Uniswap V2/V3 との違い
項目 | 従来の Uniswap (V2/V3) | UniswapX (Intent ベース) |
---|---|---|
ガス負担 | ユーザー自身 | Filler(実行者)が負担 |
取引失敗時のコスト | 失敗してもユーザーがガス消費 | ユーザーは 0 コスト、Filler がリスク |
価格探索 | ユーザーが自力でプールを選択 | 複数 Filler が競合し最適ルートを模索 |
取引形態 | プール単体でのスワップ | ダッチオーダー/リミットオーダー/… |
ユーザー体験 | 直接トランザクション送信 | “何をしたいか”署名するだけ |
Swapper の操作
UniswapX での取引は、「Swapper(ユーザー)が署名付きオーダーを作成 → Filler(実行者)がそれをオンチェーンで埋める」という流れを採用しています。ここでは、Swapper 側(いわゆる Swapper)がどのように注文を作成し、公開するのか について解説します。
オーダー作成の流れ
-
意図(Intent)の設定
- 「どのトークンを、どのくらい交換したいのか」
- 「どの価格帯や期限なら許容できるのか」
-
署名
- EIP-712 を使い、オンチェーンで検証可能な形で署名する
- Permit2 を用いてトークンの転送許可を与える署名も含まれる
- ユーザーが承認した取引のみが実行されるようにする
-
オーダーの公開
- 作成したオーダー情報をUniswapX の公開 APIまたは独自の手段で投稿し、誰でも閲覧できるようにする
- すると Filler がそれを取得し、利益が出ると判断すればオンチェーンで実行する
ポイント
- オーダーの本体は「どのトークンを支払い、どのトークンをどのくらい受け取りたいか」に加え、期限や可変ロジック(ダッチオーダーなら decay など)が含まれる。
- 署名を付与することで、ユーザーは自分の資産を第三者(Filler)に許可なく引き出されることを防ぐ。
uniswapx-sdk を使った注文作成
UniswapX が提供する uniswapx-sdk
を使うと、注文(Order)を構築し、署名を得ることができます。以下に典型的な手順を示します。
準備
import {
DutchOrder,
DutchOrderBuilder,
NonceManager,
} from "@uniswap/uniswapx-sdk";
import { BigNumber, ethers } from "ethers";
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider); // ユーザーのウォレットを用意
// 署名に使うnonceをオンチェーンから管理
const account = await wallet.getAddress();
const nonceMgr = new NonceManager(provider, 1);
const nonce = await nonceMgr.useNonce(account);
// 例としてメインネット
const chainId = 1;
const builder = new DutchOrderBuilder(chainId);
- NonceManager は、オンチェーンでの nonce を重複使用しないよう管理するためのユーティリティ。
- DutchOrderBuilder は、ダッチオーダー(時間経過で価格が変わる)を組み立てる例。ほかにも LimitOrderBuilder, PriorityOrderBuilder などが想定される。
オーダー本体の構築
const deadline = Math.floor(Date.now() / 1000) + 600; // 10分後を締切とする
const order = builder
.deadline(deadline)
.decayEndTime(deadline) // decay終了時刻=deadline
.decayStartTime(deadline - 120) // 2分前から価格を下げ始める
.nonce(nonce)
.input({
token: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
amount: BigNumber.from("1000000"), // 1.0 USDC (6桁)
})
.output({
token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
startAmount: BigNumber.from("1000000000000000000"), // 1.0 WETH
endAmount: BigNumber.from("900000000000000000"), // 0.9 WETH
recipient: "0x0000000000000000000000000000000000000000", // デフォルト(=swapper)
})
.build();
- deadline: オーダーの有効期限。ここでは 10 分
- decayStartTime / decayEndTime: ダッチオーダーにおける「価格を下げ始める時刻」と「下げ終わる時刻」
- input: 支払うトークン(USDC)と数量
- output: 受け取るトークン(WETH)の開始量と最終量
オーダー署名 + シリアライズ
const { domain, types, values } = order.permitData();
const signature = await wallet._signTypedData(domain, types, values);
const serializedOrder = order.serialize();
-
permitData()
で署名用の EIP-712 データを取得 -
_signTypedData(...)
でユーザーが自身の意図に署名 -
serialize()
でオーダーをバイナリ化し、オンチェーンでも扱いやすい形に
この結果得られた serializedOrder
(オーダーデータ) と signature
(ユーザー署名) をセットで扱うと「SignedOrder」になるイメージです。
API でもオーダーを作成できる
uniswapx-sdk を使わずに、APIを通じて注文を作成できます。たとえば、以下のようなフローがあり得ます:
- Web UI でトークン・希望レートを入力
- Metamask 等のウォレットで署名
- API へ投稿
いずれにせよ最終的には「order
+ signature
」ができあがり、オフチェーンで公開される形になります。
API ドキュメント
オーダーが完成したら
Swapper(ユーザー)が作成し、serializedOrder
と signature
をまとめた「SignedOrder」を公開すると、UniswapX の公開 API(例: /orders
)を通じて Filler がこのオーダーを発見できるようになります。
- Swapper: オーダーを公開
- Filler: 公開されたオーダー情報を取得 → execute(...) でオンチェーン実行
これにより、ユーザーの Intent が具現化された注文が実際に Filler によって埋められ、オンチェーンで取引が完了します。
Filler の実装と操作
Filler の全体像
UniswapX では、ユーザー(Swapper)が署名付きの注文(オーダー)を作成し、Filler と呼ばれる実行者がそれらをオンチェーンで実行する仕組みを採用しています。その際、Reactor と呼ばれるスマートコントラクトがトークン転送や最終的な検証を担当します。
-
Filler:
- ユーザー(Swapper)の署名済オーダーを見つけて実行するオフチェーン Bot
- 必要なら追加のアグリゲーターや MEV 戦略を用いて最適化したルートでスワップし、利益を得る
- 取引をオンチェーンに送信し、ガスコストを負担
-
Reactor:
- UniswapX の用意する「最終実行」用コントラクト
- ユーザー署名や期限、nonce などを検証し、許可されたトークン転送や Fill イベントの発行を行う
API を使用したオーダー取得
オーダー情報の取得
Filler は、UniswapX の公開 API(REST Endpoint)から“オープンなオーダー”を見つけることができます。以下は例です:
# オープンなオーダーの取得
GET https://api.uniswap.org/v2/orders?orderStatus=open&chainId=1&limit=10
# リミットオーダーの取得
GET https://api.uniswap.org/v2/limit-orders?orderStatus=open&sortKey=createdAt&desc=true
取得結果の JSON には以下のような情報が含まれます:
{
"orders": [
{
"type": "Dutch",
"orderHash": "0x...",
"orderStatus": "open",
"input": {
"token": "0x...",
"startAmount": "100",
"endAmount": "70"
},
"outputs": [
{
"token": "0x...",
"startAmount": "0.05",
"endAmount": "0.04",
"recipient": "0x..."
}
],
"decayStartTime": 1675872827,
"decayEndTime": 1675872930
}
],
"cursor": "next_page_token"
}
補足:
/limit-orders
はリミットオーダー専用のエンドポイントです。- Filler はこの他にも、Webhook を登録してリアルタイムでオーダーを受信する方法を使ったり、独自に API を監視しながらオーダーを取捨選択する場合もあります。
実行戦略の決定
Filler は取得したオーダーを基に、以下の点を分析します:
-
実行可能性の確認
- オーダーの有効期限(deadline)
- 必要なトークン量(input の数量)
- 予想される利益(スワップ差益やガスコスト差し引き後の収支)
-
実行方法の選択
- Direct Fill: 自身が保有するトークンで直接実行する
- 複数の DEX を使う: 1inch などのアグリゲーターや Uniswap V2/V3 プールを使う
-
Callback 付き実行: Reactor の
executeWithCallback
を利用して複雑なロジック(フラッシュローンや複数スワップ)を挟む
Filler はあくまで“競合的”に動くため、どの Bot がオーダーを埋めても良い仕様になっています。実行に必要な戦略ロジックのコードは公開されていませんでした。
Reactor の技術実装
UniswapX での注文実行は、Swapper(ユーザー)の署名付きオーダーをFillerがオンチェーンに送信することで行われますが、この最終ステップを請け負うのが Reactor スマートコントラクトです。Reactor はオーダーの検証からトークンの転送管理、最終的なイベント発行までを一貫して担います。
Reactor の責務
オーダーの検証と解決(_resolve)
- ユーザー署名の検証(EIP-712 など)
- オーダーの期限 (
deadline
) やnonce
(再利用防止) をチェック - 取引条件(startAmount, endAmount, decayStartTime など)の妥当性確認
- オーダーデータ (
serializedOrder
) をデコードし、最終的なResolvedOrder
にまとめる
トークンの転送管理
- Permit2 を用いたトークン転送承認の検証
- ユーザー(Swapper)から必要なトークンを回収(
_transferInputTokens
) - 取引完了後に出力トークンをユーザーや Filler、または指定された受取先に送付
- トークン転送に関する安全性確保(リプレイ攻撃防止、過剰送付の返却など)
取引の実行制御
- 時間やガス価格等の要因に応じた数量計算(ダッチオーダーの
decay
など) - コールバックロジック(
executeWithCallback
/executeBatchWithCallback
)の呼び出し - 取引完了時の
Fill
イベント 発行 - エラー・revert 処理のハンドリング
Reactor の種類と使い分け
BaseReactor.sol - 基盤となる実装
すべての Reactor が継承する抽象コントラクト。下記の主要機能を提供します。
-
注文実行系関数:
-
execute(...)
/executeBatch(...)
-
executeWithCallback(...)
/executeBatchWithCallback(...)
-
-
入力トークン回収:
Permit2
を用いてユーザーのトークンを移転 -
出力トークン送付:
_fill(...)
で複数の出力先 (outputs) にトークンを振り分け -
手数料挿入:
_injectFees(...)
でプロトコル手数料を差し引くロジック(実装任意)
DutchOrderReactor.sol - ダッチオークション型
-
時間経過でレートを変化させるロジック (
decayStartTime
,decayEndTime
) - Swapper が開始価格を高めに設定し、Filler は自分に有利な瞬間を狙う
- オーダーが長時間埋まらなければレートが下がり、Filler にとって魅力的になる
LimitOrderReactor.sol - シンプルな指値取引
- 固定価格・数量でのオーダーを想定
-
startAmount
とendAmount
が同一で、時間経過による変化なし - オーダー期限内であれば、Filler が指定価格を満たすときのみ実行
PriorityOrderReactor.sol - ガス価格連動
- tx.gasprice などネットワーク混雑に応じて数量をスケーリング
-
execute(...)
の際、Filler はガスが高い場合でも好条件を提示するか、あるいは断念するか判断可能 - ユニークな “priority fee” ベースのオーダー実装例
V2DutchOrderReactor.sol / ExclusiveDutchOrderReactor.sol / V3DutchOrderReactor.sol
- V2DutchOrderReactor: cosigner(第三者)による金額上書き機能や独占期間を組み込んだダッチオーダー
- ExclusiveDutchOrderReactor: 特定の Filler のみが一定時間オーダーを独占できる仕組み
-
V3DutchOrderReactor: 非線形
decay
やガス調整など、さらに柔軟な設定が可能
Reactor の実行フロー
Reactor に execute(...)
/ executeWithCallback(...)
が呼ばれると、概ね下記のステップで動作します。
-
_resolve
-
SignedOrder
(serializedOrder
+sig
)をデコード - ダッチ / リミット / プライオリティ 等オーダーごとのロジックを反映し、最終的な ResolvedOrder へ落とし込む
-
-
_prepare
-
order.validate(msg.sender)
で orderHash や nonce、期限などの再検証 -
_injectFees(order)
(プロトコル手数料ロジックなど) -
Permit2 署名の検証とユーザートークン転送 (
_transferInputTokens
)
-
-
(callback)
-
executeWithCallback
/executeBatchWithCallback
の場合、 - フィラー独自コントラクトの
reactorCallback(...)
を呼び出し、複数ステップのスワップロジックなどを挟める
-
-
_fill
-
outputs
に記載されたトークン量を各 recipient に送付 (transferFill(...)
) -
Fill
イベント を発行 - 余分な ETH が残っていれば Filler に返却
-
具体的実装例:LimitOrderReactor
Filler からの呼び出し
Filler は SignedOrder
を引数に以下のような呼び出しを行います。
limitOrderReactor.execute(signedOrder);
ここで signedOrder
は:
-
signedOrder.order
:LimitOrder
構造体を ABI エンコードしたバイナリ -
signedOrder.sig
: EIP-712 署名
_resolve(...)
の中身
function _resolve(SignedOrder calldata signedOrder)
internal
view
override
returns (ResolvedOrder memory resolvedOrder)
{
// LimitOrder構造体をデコード
LimitOrder memory limitOrder = abi.decode(signedOrder.order, (LimitOrder));
// リミットオーダーなので特に時間減衰等は行わない
resolvedOrder = ResolvedOrder({
info: limitOrder.info,
input: limitOrder.input,
outputs: limitOrder.outputs,
sig: signedOrder.sig,
hash: limitOrder.hash()
});
}
- LimitOrder の場合、スタート数量とエンド数量が同じであれば価格変動しないシンプルな指値注文として扱われる。
_transferInputTokens(...)
function _transferInputTokens(ResolvedOrder memory order, address to) internal override {
// Permit2により、ユーザー署名済みトークンを移転
permit2.permitWitnessTransferFrom(
order.toPermit(),
order.transferDetails(to),
order.info.swapper,
order.hash,
LimitOrderLib.PERMIT2_ORDER_TYPE,
order.sig
);
}
- 署名されたオーダー (
sig
) に基づき、ユーザートークンを安全に回収。order.info.swapper
はスワップ発行者アドレス。
まとめ
主要な改善点
- ガスフリー取引の実現:ユーザーではなく Filler がガス代を負担
- より良い取引価格:複数の流動性ソースから最適な価格を提供
- MEV 保護:フロントランニングなどの悪意のある取引からの保護
- 失敗時のコストなし:取引が不成立でもガス代が発生しない
システムの全体像
-
Swapper(ユーザー)
- 取引の意図をオフチェーンで署名
- 具体的な実行方法ではなく、望む結果のみを指定
- uniswapx-sdk や Web UI を通じて注文を作成
-
Filler(実行者)
- オーダー情報を API から取得
- 実行可能性と利益を分析
- 最適なタイミングでオンチェーン実行
-
Reactor(スマートコントラクト)
- オーダーの検証と実行を担当
- トークンの転送管理
- 複数種類の Reactor で様々な取引形態に対応
今後の展望
- クロスチェーン取引への対応
- より多様な取引形態のサポート
- Filler ネットワークの拡大による流動性の向上
UniswapX における ERC-7683 の現状
現在の UniswapX の実装では、ERC-7683(クロスチェーン対応)は実装されていませんでした。
将来的には ERC-7683 を取り入れることで、よりシームレスなクロスチェーン取引が可能になることが期待されています。
参考
コントラクト リポジトリ
API リポジトリ
SDK
ドキュメント
Discussion