UTB
0x0eb3c930944951876e2e7be8b8fe4759ffd6fb44
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
15,990 bytes
ABI entries
66
16 read · 19 write
License
none
Contract information
- Address
- 0x0eb3c930944951876e2e7be8b8fe4759ffd6fb44
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x690f4e2f19…2d23e36f4c
- Creation tx
- 0x252c87a1da…ad05e50a87
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (16)
DEFAULT_ADMIN_ROLE() → bytes32
bridgeAdapters(uint8) → address
defaultAdmin() → address
defaultAdminDelay() → uint48
defaultAdminDelayIncreaseWait() → uint48
executor() → address
feeManager() → address
getRoleAdmin(bytes32) → bytes32
hasRole(bytes32, address) → bool
isActive() → bool
owner() → address
pendingDefaultAdmin() → address, uint48
pendingDefaultAdminDelay() → uint48, uint48
supportsInterface(bytes4) → bool
swappers(uint8) → address
wrapped() → address
Events (18)
BridgeCalledDefaultAdminDelayChangeCanceledDefaultAdminDelayChangeScheduledDefaultAdminTransferCanceledDefaultAdminTransferScheduledReceivedFromBridgeRegisteredBridgeAdapterRegisteredSwapperRoleAdminChangedRoleGrantedRoleRevokedSetExecutorSetFeeManagerSetIsActiveSetWrappedSwappedWithdrawWithdrawERC20
ABI
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ExpiredFees",
"type": "error"
},
{
"inputs": [],
"name": "InvalidFees",
"type": "error"
},
{
"inputs": [],
"name": "InvalidRecipient",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSwapParams",
"type": "error"
},
{
"inputs": [],
"name": "NotEnoughNative",
"type": "error"
},
{
"inputs": [],
"name": "OnlyBridgeAdapter",
"type": "error"
},
{
"inputs": [],
"name": "OnlyWrapped",
"type": "error"
},
{
"inputs": [],
"name": "ProtocolFeeCannotBeFetched",
"type": "error"
},
{
"inputs": [],
"name": "RefundFailed",
"type": "error"
},
{
"inputs": [],
"name": "UTBPaused",
"type": "error"
},
{
"inputs": [],
"name": "WithdrawalFailed",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "txId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes4",
"name": "appId",
"type": "bytes4"
},
{
"indexed": true,
"internalType": "uint256",
"name": "dstChainId",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes4",
"name": "affiliateId",
"type": "bytes4"
},
{
"components": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct Fee[]",
"name": "fees",
"type": "tuple[]"
}
],
"indexed": false,
"internalType": "struct IUTB.TxInfo",
"name": "txInfo",
"type": "tuple"
}
],
"name": "BridgeCalled",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "DefaultAdminDelayChangeCanceled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint48",
"name": "newDelay",
"type": "uint48"
},
{
"indexed": false,
"internalType": "uint48",
"name": "effectSchedule",
"type": "uint48"
}
],
"name": "DefaultAdminDelayChangeScheduled",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "DefaultAdminTransferCanceled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "newAdmin",
"type": "address"
},
{
"indexed": false,
"internalType": "uint48",
"name": "acceptSchedule",
"type": "uint48"
}
],
"name": "DefaultAdminTransferScheduled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "txId",
"type": "bytes32"
}
],
"name": "ReceivedFromBridge",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "bridge",
"type": "address"
}
],
"name": "RegisteredBridgeAdapter",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "swapper",
"type": "address"
}
],
"name": "RegisteredSwapper",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousAdminRole",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newAdminRole",
"type": "bytes32"
}
],
"name": "RoleAdminChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "executor",
"type": "address"
}
],
"name": "SetExecutor",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "feeManager",
"type": "address"
}
],
"name": "SetFeeManager",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"name": "SetIsActive",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "wrapped",
"type": "address"
}
],
"name": "SetWrapped",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "txId",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes4",
"name": "appId",
"type": "bytes4"
},
{
"components": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes4",
"name": "affiliateId",
"type": "bytes4"
},
{
"components": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct Fee[]",
"name": "fees",
"type": "tuple[]"
}
],
"indexed": false,
"internalType": "struct IUTB.TxInfo",
"name": "txInfo",
"type": "tuple"
}
],
"name": "Swapped",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Withdraw",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "WithdrawERC20",
"type": "event"
},
{
"inputs": [],
"name": "DEFAULT_ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "acceptDefaultAdminTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newAdmin",
"type": "address"
}
],
"name": "beginDefaultAdminTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "id",
"type": "uint8"
}
],
"name": "bridgeAdapters",
"outputs": [
{
"internalType": "address",
"name": "bridgeAdapter",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "uint8",
"name": "swapperId",
"type": "uint8"
},
{
"components": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dustOut",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"internalType": "uint8",
"name": "direction",
"type": "uint8"
},
{
"internalType": "address",
"name": "refund",
"type": "address"
},
{
"internalType": "bytes",
"name": "additionalArgs",
"type": "bytes"
}
],
"internalType": "struct SwapParams",
"name": "swapParams",
"type": "tuple"
}
],
"internalType": "struct SwapInstructions",
"name": "preBridge",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint8",
"name": "swapperId",
"type": "uint8"
},
{
"components": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dustOut",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"internalType": "uint8",
"name": "direction",
"type": "uint8"
},
{
"internalType": "address",
"name": "refund",
"type": "address"
},
{
"internalType": "bytes",
"name": "additionalArgs",
"type": "bytes"
}
],
"internalType": "struct SwapParams",
"name": "swapParams",
"type": "tuple"
}
],
"internalType": "struct SwapInstructions",
"name": "postBridge",
"type": "tuple"
},
{
"internalType": "uint8",
"name": "bridgeId",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "dstChainId",
"type": "uint256"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "address",
"name": "paymentOperator",
"type": "address"
},
{
"internalType": "address",
"name": "refund",
"type": "address"
},
{
"internalType": "bytes",
"name": "payload",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "additionalArgs",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "txId",
"type": "bytes32"
}
],
"internalType": "struct BridgeInstructions",
"name": "instructions",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes4",
"name": "appId",
"type": "bytes4"
},
{
"internalType": "bytes4",
"name": "affiliateId",
"type": "bytes4"
},
{
"internalType": "uint256",
"name": "bridgeFee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct Fee[]",
"name": "appFees",
"type": "tuple[]"
}
],
"internalType": "struct FeeData",
"name": "feeData",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "bridgeAndExecute",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "cancelDefaultAdminTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint48",
"name": "newDelay",
"type": "uint48"
}
],
"name": "changeDefaultAdminDelay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "defaultAdmin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "defaultAdminDelay",
"outputs": [
{
"internalType": "uint48",
"name": "",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "defaultAdminDelayIncreaseWait",
"outputs": [
{
"internalType": "uint48",
"name": "",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "executor",
"outputs": [
{
"internalType": "contract IUTBExecutor",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeManager",
"outputs": [
{
"internalType": "contract IUTBFeeManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
}
],
"name": "getRoleAdmin",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "hasRole",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "isActive",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingDefaultAdmin",
"outputs": [
{
"internalType": "address",
"name": "newAdmin",
"type": "address"
},
{
"internalType": "uint48",
"name": "schedule",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingDefaultAdminDelay",
"outputs": [
{
"internalType": "uint48",
"name": "newDelay",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "schedule",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint8",
"name": "swapperId",
"type": "uint8"
},
{
"components": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dustOut",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"internalType": "uint8",
"name": "direction",
"type": "uint8"
},
{
"internalType": "address",
"name": "refund",
"type": "address"
},
{
"internalType": "bytes",
"name": "additionalArgs",
"type": "bytes"
}
],
"internalType": "struct SwapParams",
"name": "swapParams",
"type": "tuple"
}
],
"internalType": "struct SwapInstructions",
"name": "postBridge",
"type": "tuple"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "address",
"name": "paymentOperator",
"type": "address"
},
{
"internalType": "bytes",
"name": "payload",
"type": "bytes"
},
{
"internalType": "address",
"name": "refund",
"type": "address"
},
{
"internalType": "uint8",
"name": "bridgeId",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "txId",
"type": "bytes32"
}
],
"name": "receiveFromBridge",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "bridge",
"type": "address"
}
],
"name": "registerBridge",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "swapper",
"type": "address"
}
],
"name": "registerSwapper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "renounceRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "rollbackDefaultAdminDelay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_executor",
"type": "address"
}
],
"name": "setExecutor",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_feeManager",
"type": "address"
}
],
"name": "setFeeManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_wrapped",
"type": "address"
}
],
"name": "setWrapped",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "uint8",
"name": "swapperId",
"type": "uint8"
},
{
"components": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dustOut",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"internalType": "uint8",
"name": "direction",
"type": "uint8"
},
{
"internalType": "address",
"name": "refund",
"type": "address"
},
{
"internalType": "bytes",
"name": "additionalArgs",
"type": "bytes"
}
],
"internalType": "struct SwapParams",
"name": "swapParams",
"type": "tuple"
}
],
"internalType": "struct SwapInstructions",
"name": "swapInstructions",
"type": "tuple"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "address",
"name": "paymentOperator",
"type": "address"
},
{
"internalType": "address",
"name": "refund",
"type": "address"
},
{
"internalType": "uint256",
"name": "executionFee",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "payload",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "txId",
"type": "bytes32"
}
],
"internalType": "struct SwapAndExecuteInstructions",
"name": "instructions",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes4",
"name": "appId",
"type": "bytes4"
},
{
"internalType": "bytes4",
"name": "affiliateId",
"type": "bytes4"
},
{
"internalType": "uint256",
"name": "bridgeFee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct Fee[]",
"name": "appFees",
"type": "tuple[]"
}
],
"internalType": "struct FeeData",
"name": "feeData",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "swapAndExecute",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "id",
"type": "uint8"
}
],
"name": "swappers",
"outputs": [
{
"internalType": "address",
"name": "swapper",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "toggleActive",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdrawERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "wrapped",
"outputs": [
{
"internalType": "contract IWETH",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import {Roles} from "./utils/Roles.sol";
import {IUTB} from "./interfaces/IUTB.sol";
import {IUTBExecutor} from "./interfaces/IUTBExecutor.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {IUTBFeeManager} from "./interfaces/IUTBFeeManager.sol";
import {IBridgeAdapter} from "./interfaces/IBridgeAdapter.sol";
import {ISwapper} from "./interfaces/ISwapper.sol";
import {SwapInstructions, SwapParams, FeeData, Fee, BridgeInstructions, SwapAndExecuteInstructions} from "./CommonTypes.sol";
import {Withdrawable} from "./utils/Withdrawable.sol";
contract UTB is IUTB, Roles, Withdrawable {
constructor() Roles(msg.sender) {}
IUTBExecutor public executor;
IUTBFeeManager public feeManager;
IWETH public wrapped;
mapping(uint8 id => address swapper) public swappers;
mapping(uint8 id => address bridgeAdapter) public bridgeAdapters;
bool public isActive = true;
/**
* @dev only support calling swapAndExecute and bridgeAndExecute if active
*/
modifier isUtbActive() {
if (!isActive) revert UTBPaused();
_;
}
modifier onlyWrapped() {
if (msg.sender != address(wrapped)) revert OnlyWrapped();
_;
}
/**
* @dev Transfers fees from the sender to the fee recipients.
* @param feeData The bridge fee in native, as well as utb fee tokens and amounts.
* @param packedInfo The fees and swap instructions which were used to generate the signature.
* @param signature The ECDSA signature to verify the fee structure.
*/
function _retrieveAndCollectFees(
FeeData calldata feeData,
SwapParams memory swapParams,
bytes memory packedInfo,
bytes calldata signature
) private returns (uint256 value) {
if (feeData.chainId != block.chainid) revert InvalidFees();
if (block.timestamp > feeData.deadline) revert ExpiredFees();
if (address(feeManager) != address(0)) {
feeManager.verifySignature(packedInfo, signature);
value += feeData.bridgeFee;
Fee[] memory fees = feeData.appFees;
for (uint i = 0; i < fees.length; i++) {
Fee memory fee = fees[i];
if (fee.token != address(0)) {
SafeERC20.safeTransferFrom(
IERC20(fee.token),
msg.sender,
fee.recipient,
fee.amount
);
} else {
(bool success, ) = address(fee.recipient).call{value: fee.amount}("");
value += fee.amount;
if (!success) revert ProtocolFeeCannotBeFetched();
}
}
}
uint256 valueRequired = swapParams.tokenIn == address(0)
? swapParams.amountIn + value
: value;
if (msg.value < valueRequired) revert NotEnoughNative();
}
/**
* @dev Refunds the specified refund address.
* @param to The address receiving the refund.
* @param amount The amount of the refund.
*/
function _refundUser(address to, address token, uint256 amount) private {
if ( amount > 0 ) {
if (token == address(0)) {
(bool success, ) = to.call{value: amount}("");
if (!success) revert RefundFailed();
} else {
SafeERC20.safeTransfer(IERC20(token), to, amount);
}
}
}
/**
* @dev Sets the executor.
* @param _executor The address of the executor.
*/
function setExecutor(address _executor) public onlyAdmin {
executor = IUTBExecutor(_executor);
emit SetExecutor(_executor);
}
/**
* @dev Sets the wrapped native token.
* @param _wrapped The address of the wrapped token.
*/
function setWrapped(address _wrapped) public onlyAdmin {
wrapped = IWETH(_wrapped);
emit SetWrapped(_wrapped);
}
/**
* @dev Sets the fee manager.
* @param _feeManager The address of the fee manager.
*/
function setFeeManager(address _feeManager) public onlyAdmin {
feeManager = IUTBFeeManager(_feeManager);
emit SetFeeManager(_feeManager);
}
/**
* @dev toggles active state
*/
function toggleActive() public onlyAdmin {
isActive = !isActive;
emit SetIsActive(isActive);
}
/**
* @dev Checks if there is a swap being performed
*/
function _isSwapping(SwapParams memory swapParams) private pure returns (bool) {
return swapParams.additionalArgs.length != 0;
}
/**
* @dev Checks if there is no swap being performed
*/
function _isNotSwapping(SwapParams memory swapParams) private pure returns (bool) {
return swapParams.tokenIn == swapParams.tokenOut;
}
/**
* @dev Checks if there is a wrap being performed
*/
function _isWrapping(SwapParams memory swapParams) private view returns (bool) {
return swapParams.tokenIn == address(0) && swapParams.tokenOut == address(wrapped);
}
/**
* @dev Checks if there is an unwrap being performed
*/
function _isUnwrapping(SwapParams memory swapParams) private view returns (bool) {
return swapParams.tokenIn == address(wrapped) && swapParams.tokenOut == address(0);
}
/**
* @dev Performs a swap with the requested swapper and swap calldata.
* @param swapInstructions The swapper ID and calldata to execute a swap.
*/
function _performSwap(
SwapInstructions memory swapInstructions
) private returns (address tokenOut, uint256 amountOut, uint256 value) {
SwapParams memory swapParams = swapInstructions.swapParams;
(tokenOut, amountOut, value) = _swapHandler(swapInstructions);
amountOut -= swapParams.dustOut;
_refundUser(
swapParams.refund,
tokenOut,
swapParams.dustOut
);
}
/**
* @dev Routes swap operations to the appropriate handler based on the swap type
* @param swapInstructions The swap instructions containing swap parameters and swapper ID
*/
function _swapHandler(
SwapInstructions memory swapInstructions
) private returns (address tokenOut, uint256 amountOut, uint256 value) {
SwapParams memory swapParams = swapInstructions.swapParams;
if (_isSwapping(swapParams)) {
return _handleSwap(swapParams, swappers[swapInstructions.swapperId]);
}
if (_isNotSwapping(swapParams)) {
return _handleNoSwap(swapParams);
}
if (_isWrapping(swapParams)) {
return _handleWrap(swapParams);
}
if (_isUnwrapping(swapParams)) {
return _handleUnwrap(swapParams);
}
revert InvalidSwapParams();
}
/**
* @dev Handles a swap operation using the specified swapper contract.
* @param swapParams The swap parameters containing token addresses and amounts.
* @param swapper The address of the swapper contract to execute the swap.
*/
function _handleSwap(
SwapParams memory swapParams,
address swapper
) private returns (address tokenOut, uint256 amountOut, uint256 value) {
if (swapParams.tokenIn == address(0)) {
wrapped.deposit{value: swapParams.amountIn}();
value = swapParams.amountIn;
swapParams.tokenIn = address(wrapped);
} else {
SafeERC20.safeTransferFrom(
IERC20(swapParams.tokenIn),
msg.sender,
address(this),
swapParams.amountIn
);
}
SafeERC20.forceApprove(IERC20(swapParams.tokenIn), swapper, swapParams.amountIn);
(tokenOut, amountOut) = ISwapper(swapper).swap(swapParams);
if (tokenOut == address(0)) {
wrapped.withdraw(amountOut);
}
}
/**
* @dev Handles a direct transfer when input and output tokens are the same.
* @param swapParams The swap parameters containing token addresses and amounts.
*/
function _handleNoSwap(
SwapParams memory swapParams
) private returns (address tokenOut, uint256 amountOut, uint256 value) {
if (swapParams.tokenIn == address(0)) {
return (address(0), swapParams.amountIn, swapParams.amountIn);
} else {
SafeERC20.safeTransferFrom(
IERC20(swapParams.tokenIn),
msg.sender,
address(this),
swapParams.amountIn
);
return (swapParams.tokenIn, swapParams.amountIn, 0);
}
}
/**
* @dev Handles wrapping of native tokens to wrapped tokens.
* @param swapParams The swap parameters containing token addresses and amounts.
*/
function _handleWrap(
SwapParams memory swapParams
) private returns (address tokenOut, uint256 amountOut, uint256 value) {
wrapped.deposit{value: swapParams.amountIn}();
return (address(wrapped), swapParams.amountIn, swapParams.amountIn);
}
/**
* @dev Handles unwrapping of wrapped tokens to native tokens.
* @param swapParams The swap parameters containing token addresses and amounts.
*/
function _handleUnwrap(
SwapParams memory swapParams
) private returns (address tokenOut, uint256 amountOut, uint256 value) {
SafeERC20.safeTransferFrom(
IERC20(wrapped),
msg.sender,
address(this),
swapParams.amountIn
);
wrapped.withdraw(swapParams.amountIn);
return (address(0), swapParams.amountIn, 0);
}
/// @inheritdoc IUTB
function swapAndExecute(
SwapAndExecuteInstructions calldata instructions,
FeeData calldata feeData,
bytes calldata signature
)
public
payable
isUtbActive
{
emit Swapped(
instructions.txId,
feeData.appId,
TxInfo({
amountIn: instructions.swapInstructions.swapParams.amountIn,
tokenIn: instructions.swapInstructions.swapParams.tokenIn,
tokenOut: instructions.swapInstructions.swapParams.tokenOut,
target: instructions.target,
affiliateId: feeData.affiliateId,
fees: feeData.appFees
})
);
uint256 value = _retrieveAndCollectFees(
feeData,
instructions.swapInstructions.swapParams,
abi.encode(instructions, feeData),
signature
);
value += _swapAndExecute(
instructions.swapInstructions,
instructions.target,
instructions.paymentOperator,
instructions.payload,
instructions.refund,
instructions.executionFee
);
_refundUser(instructions.refund, address(0), msg.value - value);
}
/**
* @dev Swaps currency from the incoming to the outgoing token and executes a transaction with payment.
* @param swapInstructions The swapper ID and calldata to execute a swap.
* @param target The address of the target contract for the payment transaction.
* @param paymentOperator The operator address for payment transfers requiring ERC20 approvals.
* @param payload The calldata to execute the payment transaction.
* @param refund The account receiving any refunds, typically the EOA which initiated the transaction.
* @param executionFee Forwards additional native fees required for executing the payment transaction.
*/
function _swapAndExecute(
SwapInstructions memory swapInstructions,
address target,
address paymentOperator,
bytes memory payload,
address refund,
uint256 executionFee
) private returns (uint256 value) {
address tokenOut;
uint256 amountOut;
(tokenOut, amountOut, value) = _performSwap(swapInstructions);
if (executionFee > 0) value += executionFee;
if (tokenOut == address(0)) {
executor.execute{value: amountOut + executionFee}(
target,
paymentOperator,
payload,
tokenOut,
amountOut,
refund,
executionFee
);
} else {
SafeERC20.forceApprove(IERC20(tokenOut), address(executor), amountOut);
executor.execute{value: executionFee}(
target,
paymentOperator,
payload,
tokenOut,
amountOut,
refund,
executionFee
);
}
}
/**
* @dev Checks if the bridge token is native, and approves the bridge adapter to transfer ERC20 if required.
* @param instructions The bridge data, token swap data, and payment transaction payload.
* @param amt2Bridge The amount of the bridge token being transferred to the bridge adapter.
*/
function _approveAndCheckIfNative(
BridgeInstructions memory instructions,
uint256 amt2Bridge
) private returns (bool) {
IBridgeAdapter bridgeAdapter = IBridgeAdapter(bridgeAdapters[instructions.bridgeId]);
address bridgeToken = bridgeAdapter.getBridgeToken(
instructions.additionalArgs
);
if (bridgeToken != address(0)) {
SafeERC20.forceApprove(IERC20(bridgeToken), address(bridgeAdapter), amt2Bridge);
return false;
}
return true;
}
/// @inheritdoc IUTB
function bridgeAndExecute(
BridgeInstructions memory instructions,
FeeData calldata feeData,
bytes calldata signature
)
public
payable
isUtbActive
{
emit BridgeCalled(
instructions.txId,
feeData.appId,
instructions.dstChainId,
TxInfo({
amountIn: instructions.preBridge.swapParams.amountIn,
tokenIn: instructions.preBridge.swapParams.tokenIn,
tokenOut: instructions.postBridge.swapParams.tokenOut,
target: instructions.target,
affiliateId: feeData.affiliateId,
fees: feeData.appFees
})
);
uint256 feeValue = _retrieveAndCollectFees(
feeData,
instructions.preBridge.swapParams,
abi.encode(instructions, feeData),
signature
);
( , uint256 amt2Bridge, uint256 swapValue) = _performSwap(instructions.preBridge);
instructions.postBridge.swapParams.amountIn = amt2Bridge;
_refundUser(instructions.refund, address(0), msg.value - feeValue - swapValue);
_callBridge(amt2Bridge, feeData.bridgeFee, instructions);
}
/**
* @dev Calls the bridge adapter to bridge funds, and approves the bridge adapter to transfer ERC20 if required.
* @param amt2Bridge The amount of the bridge token being bridged via the bridge adapter.
* @param bridgeFee The fee being transferred to the bridge adapter and finally to the bridge.
* @param instructions The bridge data, token swap data, and payment transaction payload.
*/
function _callBridge(
uint256 amt2Bridge,
uint bridgeFee,
BridgeInstructions memory instructions
) private {
bool native = _approveAndCheckIfNative(instructions, amt2Bridge);
IBridgeAdapter(bridgeAdapters[instructions.bridgeId]).bridge{
value: bridgeFee + (native ? amt2Bridge : 0)
}(
IBridgeAdapter.BridgeCall({
amount: amt2Bridge,
postBridge: instructions.postBridge,
dstChainId: instructions.dstChainId,
target: instructions.target,
paymentOperator: instructions.paymentOperator,
payload: instructions.payload,
additionalArgs: instructions.additionalArgs,
refund: instructions.refund,
txId: instructions.txId
})
);
}
/// @inheritdoc IUTB
function receiveFromBridge(
SwapInstructions memory postBridge,
address target,
address paymentOperator,
bytes memory payload,
address refund,
uint8 bridgeId,
bytes32 txId
) public payable {
if (msg.sender != bridgeAdapters[bridgeId]) revert OnlyBridgeAdapter();
emit ReceivedFromBridge(txId);
_swapAndExecute(postBridge, target, paymentOperator, payload, refund, 0);
}
/// @inheritdoc IUTB
function registerSwapper(address swapper) public onlyAdmin {
ISwapper s = ISwapper(swapper);
swappers[s.ID()] = swapper;
emit RegisteredSwapper(swapper);
}
/// @inheritdoc IUTB
function registerBridge(address bridge) public onlyAdmin {
IBridgeAdapter b = IBridgeAdapter(bridge);
bridgeAdapters[b.ID()] = bridge;
emit RegisteredBridgeAdapter(bridge);
}
receive() external payable onlyWrapped {}
}
Chain explorer1360msChain node84ms