Getting Started
Last modified:
This guide walks through a contract that calls flash on a CenturionDEX v3 pool, swaps the borrowed token0 and token1 through two other pools of the same pair — each with a different fee tier — repays the original pool, and transfers any profit to the caller.
Profit comes from price-ratio differences between pools, not from the fee tiers themselves. Fee tiers influence execution cost, but the arbitrage opportunity exists because pools at different fee tiers can settle at different prices. The contract exploits that divergence: borrow from one pool, swap through the others at more favorable ratios, repay principal plus fee, and keep the remainder.
Gas costs, available liquidity, slippage, and market volatility all affect whether a given opportunity is actually profitable. This code is an educational example — do your own analysis before risking real funds.
Flash transactions overview
A flash transaction transfers tokens to the caller before the conditions for keeping them are satisfied. In the context of a swap, the output is sent before the input is received.
CenturionDEX v3 exposes this via the flash function on the Pool contract. flash sends specified amounts of token0 and token1 to a recipient; principal plus the pool's swap fee is due back by the end of the same transaction. A fourth parameter, data, lets the caller ABI-encode arbitrary context to be decoded in the callback.
function flash(
address recipient,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external override lock noDelegateCall {The flash callback
Midway through flash, the pool invokes the callback on msg.sender:
ICenturionV3FlashCallback(msg.sender).centurionV3FlashCallback(fee0, fee1, data);This passes the fee amounts needed to calculate the debt, along with the caller-supplied data. V3 defines three callback hooks — centurionV3SwapCallback, centurionV3MintCallback, and centurionV3FlashCallback — each overridable with custom logic. Our contract overrides centurionV3FlashCallback to perform the arbitrage swaps and repay the pool.
Contract setup
Inherit ICenturionV3FlashCallback (to override the callback) and PeripheryPayments (for token transfer helpers). These base contracts pull in additional utilities we rely on, such as LowGasSafeMath, which we attach to uint256 and int256.
contract PairFlash is ICenturionV3FlashCallback, PeripheryPayments {
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;Declare an immutable swapRouter so the contract can call exactInputSingle during the callback:
ISwapRouter public immutable swapRouter;The constructor wires up the router, factory, and WCTN9 addresses. PeripheryImmutableState stores factory and WCTN9 for use by inherited helpers.
constructor(
ISwapRouter _swapRouter,
address _factory,
address _WETH9
) PeripheryImmutableState(_factory, _WETH9) {
swapRouter = _swapRouter;
}Full imports and declaration
pragma solidity =0.7.6;
pragma abicoder v2;
import '@centurion-dex/v3-core/contracts/interfaces/callback/ICenturionV3FlashCallback.sol';
import '@centurion-dex/v3-core/contracts/libraries/LowGasSafeMath.sol';
import '@centurion-dex/v3-periphery/contracts/base/PeripheryPayments.sol';
import '@centurion-dex/v3-periphery/contracts/base/PeripheryImmutableState.sol';
import '@centurion-dex/v3-periphery/contracts/libraries/PoolAddress.sol';
import '@centurion-dex/v3-periphery/contracts/libraries/CallbackValidation.sol';
import '@centurion-dex/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@centurion-dex/v3-periphery/contracts/interfaces/ISwapRouter.sol';
contract PairFlash is ICenturionV3FlashCallback, PeripheryPayments {
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;
ISwapRouter public immutable swapRouter;
constructor(
ISwapRouter _swapRouter,
address _factory,
address _WETH9
) PeripheryImmutableState(_factory, _WETH9) {
swapRouter = _swapRouter;
}
}