Trong quá trình nghiên cứu tìm hiểu một số bot trên blockchain Pharos Network, mình có thấy một giao dịch khá đặc biệt: 0x15aa5fd90c0f7623928fc67116f5d472d42cbde25e695efb6d7a4f5d2d705427. Giao dịch này thực hiện swap khá phức tạp, giao dịch trên shitcoin, lại có đoạn chuyển đổi từ USDC sang bUSDC khá khó hiểu. Vì thế mình quyết định đi sâu tìm hiển và phân tích giao dịch này, qua đó hiểu được chiến lược mà bot này đang sử dụng.
Mục lục
Phân tích chi tiết giao dịch
Từ tab Overview của giao dịch, trong phần “ Token Transferred“, từ thông tin này chúng ta có thể dựng lại luồng thực hiện. Tổng cộng có 10 lệnh transfer, tôi tách thành 2 phần như dưới. Bạn có thể xem thêm tab Logs và Trace để có nhiều thông tin chi tiết hơn.
Phần 1: Giao dịch arbitrage
Phần đầu, bot thực hiện một giao dịch chênh lệch giá giữa hai pool FaroSwapV3-FaroSwapV2, với luồng token BTCVP-CFVP-BTCVP => Lợi nhuận thu được là 0.00008384 BTCVP. Đây chỉ là một giao dịch “Cyclic Arbitrage” thông thường.

Kết hợp với thông tin trong phần Trace, ta có thể mô tả luồng sơ bộ như sau:
- B1: Bot thực hiện swap BTCVP->CFVP trên FaroSwapV3 để nhận về 0.437652865216328 CFVP. Với V3 khi swap bạn không cần phải đưa trước token BTCVP.
- B2: Bot chuyển 0.437652865216328 CFVP vào V2 và thực hiện swap trên FaroSwapV2 để nhận về 0.0073822 BTCVP
- B3: Bot thực hiện hoàn trả 0.00729836 BTCVP cho FaroSwapV3 để hoàn thành arbitrage.
- B4: 0.00008384 BTCVP còn lại chính là lợi nhuận.
Phần 2: Chuyển lợi nhuận sang WPROS
Phần này dù quá trình chuyển đổi phức tạp nhưng thực chất là quá trình chuyển đổi lợi nhuận từ BTCVP sang WPROS.

Kết hợp với thông tin trong phần Trace, ta có thể mô tả luồng sơ bộ như sau:
- B1: Bot thực hiện bán toàn bộ lợi nhuận ở dạng BTCVP qua pool FaroSwapV2 để nhận được 0.0684 USDC.
- B2: Bot thực hiện bán USDC thông qua pool DODO DLP để nhận được 0.081427 bUSDC thông qua hàm querySellBase() (Lấy giá) và sellBase() (Bán).
- B3: Thực hiện swap bUSDC-> WPROS thông qua Pool FaroSwapV2 để chốt lợi nhuận 0.17 WPROS.
Tôi đã tìm hiểu token bUSDC, đây là token của giao thức lending OpenFi. Khi chúng ta thực hiện deposit USDC vào giao thức, ta sẽ nhận được một lượng token bUSDC với số lượng tương ứng.
Trong giao dịch trên thì khi đi qua giao thức DODO thì lại nhận được nhiều bUSDC hơn. Tôi đã kiểm tra thì ứng dụng DODO chuẩn không hỗ trợ mạng lưới Pharos. Nhưng FaroSwap lại triển khai phiên bản DODOV2 và giao diện sử dụng DoDoV2 để thực hiện swap. Tôi kiểm tra trên giao diện của FaroSwap khi hoán đổi từ USDC->bUSDC, nhiều khi nhận được nhiều bUSDC hơn. Như vậy giao dịch trên đã tận dụng DODO DLP để tăng thêm lợi nhuận.
Tương tác với DODO API để lấy route tối ưu
Trong giao dịch trên thì phần phức tạp nhất là route trong phần thứ 2, tìm route tối ưu để chuyển đổi từ BTCVP sang WPROS. Phần này tự làm thì tôi nghĩ rất khó mà khả năng cao là sử dụng DODO API. Trong tài liệu của FaroSwap cũng có hướng dẫn phần này, chi tiết xem: API Integration
Dưới đây là đoạn code lấy quote qua DODO API sử dụng NodeJs:
const axios = require('axios').default;
const qs = require('qs');
const dodoApi = "https://api.dodoex.io/route-service/v2/widget/getdodoroute";
// Đăng ký tại https://developer.dodoex.io
const apiKey = "<API Key của bạn>";
async function getQuoteInfo(chainId, userAddr, fromTokenAddr, toTokenAddr, fromAmount) {
let deadline = Math.floor(Date.now()/1000) + 60;
let params = {
chainId: chainId,
apikey: apiKey,
source: "dodoV2AndMixWasm",
userAddr: userAddr,
fromTokenAddress: fromTokenAddr,
toTokenAddress: toTokenAddr,
deadLine: deadline,
slippage: 3.225,
estimateGas: true,
fromAmount: fromAmount
};
let queryString = qs.stringify(params);
let link = `${dodoApi}?${queryString}`;
//console.log("Link:", link);
try {
let resp = await axios.get(link);
if (resp.status!=200) {
console.error("[1] Error to call DODO API", resp.statusText);
return null;
}
let data = resp.data;
if (data.status!=200) {
console.error("[2] Error to call DODO API", data);
return null;
}
return data.data;
} catch(ex) {
console.error("Error to call DODO API", ex);
}
return null;
}
async function main() {
let chainId = 1672; // Pharos
let userAddr = "0x0146276f02864a8E5780A1dcdAdB065B951f7771";
let fromTokenAddr = "0xC879C018dB60520F4355C26eD1a6D572cdAC1815"; // USD Coin (USDC)
let toTokenAddr = "0x9dcf4b664fd2c8f0f5147ea469afe1cbc9e69d96"; // OpenFi USDC (bUSDC)
let fromAmount = "20000"; // 0.02 USDC
let quoteInfo = await getQuoteInfo(chainId, userAddr, fromTokenAddr, toTokenAddr, fromAmount);
console.log("QuoteInfo:", JSON.stringify(quoteInfo, null, 4));
}
main();
Kết quả nhận được như sau:
QuoteInfo: {
"resAmount": 0.0201347525,
"baseFeeAmount": 0.000030247499999999998,
"baseFeeRate": 0.0015,
"resPricePerToToken": 0.9933074667791423,
"resPricePerFromToken": 1.006737625,
"priceImpact": 0,
"useSource": "DODORoute",
"targetDecimals": 6,
"targetApproveAddr": "0xBF105f4ffBD3825f5433d074008B9A76237d849c",
"to": "0xA5cA5Fbe34e444F366B373170541ec6902b0F75c",
"data": "0xff84aafa000000000000000000000000c879c018db60520f4355c26ed1a6d572cdac18150000000000000000000000009dcf4b664fd2c8f0f5147ea469afe1cbc9e69d960000000000000000000000000000000000000000000000000000000000004e200000000000000000000000000000000000000000000000000000000000004ea60000000000000000000000000000000000000000000000000000000000004c1d000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000460000000000000000000000000000000000000000000000000000000006a55ef6800000000000000000000000000000000000000000000000000000000000000020000000000000000000000004fd44181839d24e7c8f4d1b9288379109ec25fae0000000000000000000000003084d27a9a385ea77a13d01b961632b8ada23bbd0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000912c9ade24d44d8922f0866d8dcb079f1363f647000000000000000000000000e73d84c13735dd10f6b2d7d816ab910306a0a44a00000000000000000000000000000000000000000000000000000000000000030000000000000000000000004fd44181839d24e7c8f4d1b9288379109ec25fae000000000000000000000000e73d84c13735dd10f6b2d7d816ab910306a0a44a000000000000000000000000a5ca5fbe34e444f366b373170541ec6902b0f75c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c879c018db60520f4355c26ed1a6d572cdac181500000000000000000000000052c48d4213107b20bc583832b0d951fb9ca8f0b000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"minReturnAmount": "19485",
"gasLimit": "657271",
"routeInfo": {
"subRouteTotalPart": 100,
"subRoute": [
{
"midPathPart": 100,
"midPath": [
{
"fromToken": "0xc879c018db60520f4355c26ed1a6d572cdac1815",
"toToken": "0x52c48d4213107b20bc583832b0d951fb9ca8f0b0",
"oneSplitTotalPart": 20,
"poolDetails": [
{
"poolName": "DODOAmmV3",
"pool": "0x912c9ade24d44d8922f0866d8dcb079f1363f647",
"pairId": "0x912c9ade24d44d8922f0866d8dcb079f1363f647",
"poolPart": 20,
"poolInAmount": "20000",
"poolOutAmount": "52572016006148241",
"updatedAt": 1784016542
}
],
"fromAmount": "20000",
"toAmount": "52572016006148241"
},
{
"fromToken": "0x52c48d4213107b20bc583832b0d951fb9ca8f0b0",
"toToken": "0x9dcf4b664fd2c8f0f5147ea469afe1cbc9e69d96",
"oneSplitTotalPart": 20,
"poolDetails": [
{
"poolName": "DODOAmmV2",
"pool": "0xe73d84c13735dd10f6b2d7d816ab910306a0a44a",
"pairId": "0xe73d84c13735dd10f6b2d7d816ab910306a0a44a",
"poolPart": 20,
"poolInAmount": "52572016006148241",
"poolOutAmount": "20165",
"updatedAt": 1783940414
}
],
"fromAmount": "52572016006148241",
"toAmount": "20165"
}
]
}
]
},
"value": "0",
"rpcCounter": 1,
"id": "cadd2709b54f46eb0ed46de4959dd56c"
}
Dựa vào thông tin của DODO API trả về chúng ta có thể thực hiện giao dịch:
- B1: Thực hiện lệnh approve cho phép spender (targetApproveAddr) sử dụng token
- B2: Thực hiện giao dịch từ thông tin (to, data, value)
Kiểm tra thêm một số giao dịch khác của bot
Tôi đi sâu hơn tìm hiểu thêm một số giao dịch khác liên quan tới contract 0x124c8090216d3d0ebe4f24bddc1ab958a7fd41ea để có cái nhìn tổng quan hơn về con bot này. Nhìn phần Trace giao dịch thì nghiệp vụ xử lý chính nằm ở contract: 0x1fe7fabfe9f1d35c2ecd9427b93ae0a781b4fb87
Sau khi xem xét một loạt các giao dịch như:
- Các giao dịch gần đây đều thực hiện Cyclic Arbitrage theo path: CFVP-BTCVP-CFVP và BTCVP-CFVP-BTCVP, sau đó lợi nhuận thu được chuyển thành WPROS
- 0xdf2622b236b631902accdb0d7e46490bb1294839de5beef513d6f95c4fa4f9ff
- 0xe79ae993bf7f083599e8fe0134bc0fe2d24248fa5b872d63826c57601cc75886
- 0x8b67ca591405bb979b705b4737191b8e5d116bfae108fa79c21f7f9b0aed3c50
- 0x828f4fc72356c31b52c09e05047826daa5b6d8889eeb200a5939f3086ad26925
- 0xdd5aeba332e8719c87a3c39e271a7c7fcffc622ea6b888a478c06117b4e0a68d
- 0x0b00e48638f3d03e92b580d15343984992ef4d1f2f677b9ea1fff1fc87cc2231
- …
- Thời điểm đầu mới tạo contract thì giao dịch cặp PDOG-BOO-USDC-PDOG, lợi nhuận thu được chuyển về WPROS:
- Cặp PDOG-MGRT-USDC-PDOG:
- 0x000c0fc6a3af0f97389110fae7cedd3b2f2f6fe089e2afc66e484c45416eac46 => Có cả pool V2, V3 và V4
- 0x38a0d851335dad4db32a23e6570dc4ac1fc89ebf86cee034293316584464e76a => Hỗ trợ cả DODO Pool
- 0x525f9043b75daeb233e3e9d6b0e9ab02bf86413e6a4d1e9c05a666adf653ff3b
Tất cả
Nhưng nếu DODO gọi qua API thì phải biết trước profit, nhưng thực tế profit có thể thay đổi. Mặt khác, nếu sử dụng DODO API thì contract sẽ phải gọi tới DODOFeeRouteProxy để thực hiện swap, nhưng trong phần Trace hoàn toàn không thấy sự xuất hiện của contract này => Loại bỏ khả năng sử dụng DODO API trước.
Kiểm tra thêm phần “Input Data” phát hiện tất cả các pool sử dụng để thực hiện Cyclic Arbitrage và Hoán đổi về WPROS đều xuất hiện trong phần “Input Data”. Như vậy thông tin các pool được Bot truyền lên trước. “Input Data” rất ít số 0 chứng tỏ sử dụng cơ chế tự encode/decode để tối ưu phần “Input Data“.
Tôi nghĩ khả năng cao, vẫn sử dụng DODO API với profit dự kiến, sử dụng thông tin trong trường routeInfo thay vì sử dụng data đã được encode sẵn trong trường data. Nếu vậy thì code Smart Contract để thực hiện hoán đổi sẽ khá là phức tạp.
Đánh giá chiến lược của bot
Đây là bot thuộc nhóm “Cyclic Arbitrage” với nhiều tính năng và lợi thế hơn các bot khác:
- Chiến lược bot gồm 2 phần chính:
- Phần “Cyclic Arbitrage”: Bot có hệ thống theo dõi cơ hội offchain, và chỉ thực hiện giao dịch khi có cơ hội. Thông tin path có cơ hội được truyền qua “Input Data” của giao dịch.
- Phần swap về token chính: Bot cũng tự động tính toán swap path lúc offchain, truyền thông tin này vào “Input Data” của giao dịch.
- Hỗ trợ trade vòng nhiều loại token, bao gồm cả token chính của chain và cả shitcoin
- Với token là shitcoin, thì có cơ chế tự động swap lợi nhuận về token chính
- Contract hỗ trợ nhiều pool khác nhau bao gồm cả V2, V3, V4, Stable Pool và DODO Pool
Đây thực sự đây là bot rất mạnh, có thể gọi là “Universal Arbitrage Executor“. Bot này mạnh ở cả phần Smart Contract và phần Backend. Phần Backend phải giải quyết đồng thời hai bài toán:
- Arbitrage Search
- Smart Routing: Giống với DEXpert đã làm

Có thể kiến trúc bot như sau:

Các pool mà bot này hỗ trợ cũng khá giống với DODO DEXpert. Mình nghi ngờ bot này cũng được xây dựng bởi team DEXpert, hoặc cùng công ty với DEXpert.
Mình là lập trình viên, mặc dù cũng đã khá lớn tuổi nhưng vẫn thích Lập trình. Gần đây mình tập trung tìm hiểu nhiều hơn về Lĩnh vực Blockchain. Với kiến thức tìm hiểu được, mình muốn viết ra để lưu lại cũng như để chia sẻ cho những người quan tâm. Mong mọi người góp ý và có thể cùng mình chia sẻ nhiều kiến thức hơn cho cộng đồng.







Trả lời