Contract detail
Swapper
Live Robinhood Chain account facts for 0x0a42fa17…89cf8eb0 with at least 19 observed transactions. The history total is updating as indexing continues.
- Address type
- Smart contract
- Transactions
- 19+ observed
- First indexed block
- #48327
- Latest indexed block
- #72646074
- History coverage
- Recent activity available; total is catching up
- Indexed token transfers
- 888
- ERC-20 transfers
- 2202
- Emitted events
- 245
- Verification
- Verified
- Compiler
- v0.8.33+commit.64118f21
- Verification match
- partial
- Creation transaction
- 0xe835464f…9bd2d8f0
- Created at block
- #48327
- Language
- solidity
- EVM version
- prague
- License
- none
- Compiler optimization
- Enabled · 100 runs
Related activity
Transactions
Verified contract
Contract source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {AccessControl} from "../lib/openzeppelin-contracts/contracts/access/AccessControl.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol";
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {ISwapRoute} from "./interfaces/ISwapRoute.sol";
import {ISwapper} from "./interfaces/ISwapper.sol";
struct SwapRoute {
ISwapRoute swapper;
uint32 routeId;
}
struct Segment {
ISwapRoute swapper;
uint32 routeId;
uint32 next;
}
contract Swapper is ISwapper, AccessControl {
using SafeERC20 for IERC20;
event SegmentAdded(
IERC20 source,
IERC20 destination,
uint256 step,
uint256 segmentId,
ISwapRoute swapper,
uint32 routeId,
uint32 nextSegmentId
);
event RouteSet(IERC20 source, IERC20 destination, uint32 firstSegment);
event Sold(
IERC20 source,
IERC20 destination,
uint256 input,
uint256 output
);
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
uint32 constant NO_SEGMENT = 0;
mapping(IERC20 => mapping(IERC20 => uint32)) firstSegments;
mapping(uint32 => Segment) segments;
uint32 segmentsIdx = 1;
address private immutable STEAKHOUSE_SIGNER_1 =
0x0D61C8b6CA9669A36F351De3AE335e9689dd9C5b;
address private immutable STEAKHOUSE_SMOL_OPS =
0x0000aeB716a0DF7A9A1AAd119b772644Bc089dA8;
// 0x0000aeB716a0DF7A9A1AAd119b772644Bc089dA8 on Base
// 0x29d4CDFee8F533af8529A9e1517b580E022874f7 on Ethereum
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(DEFAULT_ADMIN_ROLE, STEAKHOUSE_SIGNER_1);
_grantRole(DEFAULT_ADMIN_ROLE, STEAKHOUSE_SMOL_OPS);
_grantRole(OPERATOR_ROLE, msg.sender);
_grantRole(OPERATOR_ROLE, STEAKHOUSE_SIGNER_1);
_grantRole(OPERATOR_ROLE, STEAKHOUSE_SMOL_OPS);
}
function setRoute(
IERC20 tokenFrom,
IERC20 tokenTo,
SwapRoute[] memory route
) public onlyRole(OPERATOR_ROLE) {
require(route.length > 0, "Route shouldn't be empty");
SwapRoute memory routeSegment;
uint32 nextSegmentId = NO_SEGMENT;
for (uint256 i = route.length - 1; i > 0; i--) {
routeSegment = route[i];
segments[segmentsIdx] = Segment(
routeSegment.swapper,
routeSegment.routeId,
nextSegmentId
);
emit SegmentAdded(
tokenFrom,
tokenTo,
i,
segmentsIdx,
routeSegment.swapper,
routeSegment.routeId,
nextSegmentId
);
nextSegmentId = segmentsIdx;
segmentsIdx += 1;
}
routeSegment = route[0];
segments[segmentsIdx] = Segment(
routeSegment.swapper,
routeSegment.routeId,
nextSegmentId
);
firstSegments[tokenFrom][tokenTo] = segmentsIdx;
emit SegmentAdded(
tokenFrom,
tokenTo,
0,
segmentsIdx,
routeSegment.swapper,
routeSegment.routeId,
nextSegmentId
);
emit RouteSet(tokenFrom, tokenTo, segmentsIdx);
segmentsIdx += 1;
}
function resetRoute(
IERC20 tokenFrom,
IERC20 tokenTo,
uint32 firstSegment
) public onlyRole(OPERATOR_ROLE) {
require(firstSegment != NO_SEGMENT, "First segment id can't be 0");
require(
firstSegment < segmentsIdx,
"First segment id outside what exist"
);
firstSegments[tokenFrom][tokenTo] = firstSegment;
emit RouteSet(tokenFrom, tokenTo, firstSegment);
}
/// @notice Sell a token for another one, if amount is 0 all the balance is sold
function sell(
IERC20 tokenFrom,
IERC20 tokenTo,
uint256 amountIn
) public returns (uint256) {
// Get the first segment of the route
uint32 nextSegment = firstSegments[tokenFrom][tokenTo];
require(nextSegment != NO_SEGMENT, "No swapping route");
// If no amount was given, use the balance
if (amountIn == 0) amountIn = tokenFrom.balanceOf(address(msg.sender));
tokenFrom.safeTransferFrom(address(msg.sender), address(this), amountIn);
while (nextSegment != NO_SEGMENT) {
Segment memory segment = segments[nextSegment];
(IERC20 token, address who, uint256 amount) = segment
.swapper
.approvalNeeded(segment.routeId);
token.forceApprove(who, amount);
segment.swapper.sell(segment.routeId);
nextSegment = segment.next;
}
uint256 amountOut = tokenTo.balanceOf(address(this));
tokenTo.safeTransfer(address(msg.sender), amountOut);
emit Sold(tokenFrom, tokenTo, amountIn, amountOut);
return amountOut;
}
/// @notice sell a token for another one with a expected return and allowed deviation
/// @param maxSlippage 6 digit allowed deviation 200 means 2 bips
function sellWithLimit(
IERC20 tokenFrom,
IERC20 tokenTo,
uint256 amountIn,
uint256 expected,
uint256 maxSlippage
) external returns (uint256) {
uint256 amountOut = sell(tokenFrom, tokenTo, amountIn);
uint256 minAmount = (expected * (1000000 - maxSlippage)) / 1000000;
require(
amountOut >= minAmount,
"Revert due to amountOut being below allowed"
);
return amountOut;
}
/// @notice sell a token for another one with a minAmountOut
/// @param minAmountOut minimum amount of tokenTo or it revert
function sellWithThreshold(
IERC20 tokenFrom,
IERC20 tokenTo,
uint256 amountIn,
uint256 minAmountOut
) external returns (uint256) {
uint256 amountOut = sell(tokenFrom, tokenTo, amountIn);
require(
amountOut >= minAmountOut,
"Revert due to amountOut being below allowed"
);
return amountOut;
}
function previewSell(
IERC20 tokenFrom,
IERC20 tokenTo,
uint256 amountIn
) external returns (uint256) {
// Get the first segment of the route
uint32 nextSegment = firstSegments[tokenFrom][tokenTo];
require(nextSegment != NO_SEGMENT, "No swapping route");
// If no amount was given, use the balance
if (amountIn == 0) amountIn = tokenFrom.balanceOf(address(msg.sender));
uint256 current = amountIn;
while (nextSegment != NO_SEGMENT) {
Segment memory segment = segments[nextSegment];
current = segment.swapper.previewSell(segment.routeId, current);
nextSegment = segment.next;
}
return current;
}
/******************************************
* RECOVER AND UNSTUCK FUNCTIONS
******************************************/
function recover(
IERC20 token,
address where,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(
amount <= token.balanceOf(address(this)),
"Insufficient balance"
);
token.safeTransfer(where, amount);
}
function recover(
IERC20 token,
address where
) external onlyRole(DEFAULT_ADMIN_ROLE) {
token.safeTransfer(where, token.balanceOf(address(this)));
}
function recoverETH(
address payable where,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(amount <= address(this).balance, "Insufficient balance");
where.transfer(amount);
}
function approve(
IERC20 token,
address where,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) {
token.forceApprove(where, amount);
}
}