// SPDX-License-Identifier: MIT
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol
// and modified it.
pragma solidity ^0.8.0;
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
function functionCallWithValue(address target, bytes memory data, uint256 weiValue) internal returns (bytes memory) {
require(data.length == 0 || isContract(target), "transfer or contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
if (success) {
return returndata;
} else if (returndata.length > 0) {
assembly{
revert (add (returndata, 0x20), mload (returndata))
}
} else {
revert("failed");
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../ERC20/IERC20.sol";
interface IBrokerbot {
function base() external view returns (IERC20);
function settings() external view returns (uint256);
// @return The amount of shares bought on buying or how much in the base currency is transfered on selling
function processIncoming(IERC20 token_, address from, uint256 amount, bytes calldata ref) external payable returns (uint256);
}
/**
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2016-2019 zOS Global Limited
*
*/
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see `ERC20Detailed`.
*/
interface IERC20 {
// Optional functions
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a `Transfer` event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through `transferFrom`. This is
* zero by default.
*
* This value changes when `approve` or `transferFrom` are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* > Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an `Approval` event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a `Transfer` event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to `approve`. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
// https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/IQuoter.sol
pragma solidity ^0.8.0;
interface IQuoter {
function quoteExactOutputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountOut,
uint160 sqrtPriceLimitX96
) external returns (uint256 amountIn);
/// @notice Returns the amount in required for a given exact output swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
/// @param amountOut The amount of the last token to receive
/// @return amountIn The amount of first token required to be paid
function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn);
// solhint-disable-next-line func-name-mixedcase
function WETH9() external view returns (address);
}
interface ISwapRouter {
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
function refundETH() external payable;
}
// SPDX-License-Identifier: MIT
//
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
//
// Modifications:
// - Replaced Context._msgSender() with msg.sender
// - Made leaner
// - Extracted interface
pragma solidity ^0.8.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor (address initialOwner) {
owner = initialOwner;
emit OwnershipTransferred(address(0), owner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) external onlyOwner {
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
modifier onlyOwner() {
require(owner == msg.sender, "not owner");
_;
}
}
/**
* SPDX-License-Identifier: LicenseRef-Aktionariat
*
* MIT License with Automated License Fee Payments
*
* Copyright (c) 2021 Aktionariat AG (aktionariat.com)
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - All automated license fee payments integrated into this and related Software
* are preserved.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity ^0.8.0;
import "../utils/Address.sol";
import "../ERC20/IERC20.sol";
import "./IUniswapV3.sol";
import "../utils/Ownable.sol";
import "./IBrokerbot.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
/**
* A hub for payments. This allows tokens that do not support ERC 677 to enjoy similar functionality,
* namely interacting with a token-handling smart contract in one transaction, without having to set an allowance first.
* Instead, an allowance needs to be set only once, namely for this contract.
* Further, it supports automatic conversion from Ether to the payment currency through Uniswap or the reception of Ether
* using the current exchange rate as found in the chainlink oracle.
*/
contract PaymentHub {
uint24 private constant DEFAULT_FEE = 3000;
uint256 private constant DENOMINATOR = 1e8;
address private constant CHF_TOKEN = 0xB4272071eCAdd69d933AdcD19cA99fe80664fc08;
uint8 private constant KEEP_ETHER = 0x4; // copied from brokerbot
IQuoter private immutable uniswapQuoter;
ISwapRouter private immutable uniswapRouter;
AggregatorV3Interface internal immutable priceFeedCHFUSD;
AggregatorV3Interface internal immutable priceFeedETHUSD;
constructor(IQuoter _quoter, ISwapRouter swapRouter, AggregatorV3Interface _aggregatorCHFUSD, AggregatorV3Interface _aggregatorETHUSD) {
uniswapQuoter = _quoter;
uniswapRouter = swapRouter;
priceFeedCHFUSD = _aggregatorCHFUSD;
priceFeedETHUSD = _aggregatorETHUSD;
}
/*
* Get price in ERC20
* This is the method that the Brokerbot widget should use to quote the price to the user.
* @param amountInBase The amount of the base currency for the exact output.
* @param path The encoded path of the swap from erc20 to base.
* @return amount quoted to pay
*/
function getPriceInERC20(uint256 amountInBase, bytes memory path) public returns (uint256) {
return uniswapQuoter.quoteExactOutput(
path,
amountInBase
);
}
/**
* Get price in Ether depding on brokerbot setting.
* If keep ETH is set price is from oracle.
* This is the method that the Brokerbot widget should use to quote the price to the user.
* @return The price in wei.
*/
function getPriceInEther(uint256 amountInBase, IBrokerbot brokerBot) public returns (uint256) {
if ((address(brokerBot) != address(0)) && hasSettingKeepEther(brokerBot)) {
return getPriceInEtherFromOracle(amountInBase, IBrokerbot(brokerBot).base());
} else {
return uniswapQuoter.quoteExactOutputSingle(uniswapQuoter.WETH9(), address(brokerBot.base()), DEFAULT_FEE, amountInBase, 0);
}
}
/**
* Price in ETH with 18 decimals
*/
function getPriceInEtherFromOracle(uint256 amountInBase, IERC20 base) public view returns (uint256) {
if(address(base) == CHF_TOKEN) {
return getLatestPriceCHFUSD() * amountInBase / getLatestPriceETHUSD();
} else {
return amountInBase * DENOMINATOR / getLatestPriceETHUSD();
}
}
/**
* Returns the latest price of eth/usd pair from chainlink with 8 decimals
*/
function getLatestPriceETHUSD() public view returns (uint256) {
(, int256 price, , , ) = priceFeedETHUSD.latestRoundData();
return uint256(price);
}
/**
* Returns the latest price of chf/usd pair from chainlink with 8 decimals
*/
function getLatestPriceCHFUSD() public view returns (uint256) {
(, int256 price, , , ) = priceFeedCHFUSD.latestRoundData();
return uint256(price);
}
/**
* Convenience method to swap ether into base and pay a target address
*/
function payFromEther(address recipient, uint256 amountInBase, IERC20 base) public payable {
ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams(
// rely on time stamp is ok, no exact time stamp needed
// solhint-disable-next-line not-rely-on-time
uniswapQuoter.WETH9(), address(base), DEFAULT_FEE, recipient, block.timestamp, amountInBase, msg.value, 0);
ISwapRouter swapRouter = uniswapRouter;
// Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
uint256 amountIn = swapRouter.exactOutputSingle{value: msg.value}(params);
// For exact output swaps, the amountInMaximum may not have all been spent.
// If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
if (amountIn < msg.value) {
swapRouter.refundETH();
(bool success, ) = msg.sender.call{value:msg.value - amountIn}(""); // return change
require(success, "Transfer failed.");
}
}
/// @dev The calling address must approve this contract to spend its ERC20 for this function to succeed. As the amount of input ERC20 is variable,
/// the calling address will need to approve for a slightly higher or infinit amount, anticipating some variance.
/// @param amountOut The desired amount of baseCurrency.
/// @param amountInMaximum The maximum amount of ERC20 willing to be swapped for the specified amountOut of baseCurrency.
/// @param erc20In The address of the erc20 token to pay with.
/// @param path The encoded path of the swap from erc20 to base.
/// @param recipient The reciving address - brokerbot.
/// @return amountIn The amountIn of ERC20 actually spent to receive the desired amountOut.
function payFromERC20(uint256 amountOut, uint256 amountInMaximum, address erc20In, bytes memory path, address recipient) public returns (uint256 amountIn) {
ISwapRouter swapRouter = uniswapRouter;
// Transfer the specified `amountInMaximum` to this contract.
IERC20(erc20In).transferFrom(msg.sender, address(this), amountInMaximum);
// The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
ISwapRouter.ExactOutputParams memory params =
ISwapRouter.ExactOutputParams({
path: path,
recipient: recipient,
// solhint-disable-next-line not-rely-on-time
deadline: block.timestamp,
amountOut: amountOut,
amountInMaximum: amountInMaximum
});
// Executes the swap, returning the amountIn actually spent.
amountIn = swapRouter.exactOutput(params);
// If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
if (amountIn < amountInMaximum) {
IERC20(erc20In).transfer(msg.sender, amountInMaximum - amountIn);
}
}
///This function appoves infinite allowance for Uniswap, this is safe as the paymenthub should never hold any token (see also recover() ).
///@dev This function needs to be called before using the PaymentHub the first time with a new ERC20 token.
///@param erc20In The erc20 addresse to approve.
function approveERC20(address erc20In) external {
IERC20(erc20In).approve(address(uniswapRouter), 0x8000000000000000000000000000000000000000000000000000000000000000);
}
function multiPay(IERC20 token, address[] calldata recipients, uint256[] calldata amounts) public {
for (uint i=0; i<recipients.length; i++) {
require(IERC20(token).transferFrom(msg.sender, recipients[i], amounts[i]));
}
}
/**
* Can (at least in theory) save some gas as the sender balance only is touched in one transaction.
*/
function multiPayAndNotify(IERC20 token, address[] calldata recipients, uint256[] calldata amounts, bytes calldata ref) external {
for (uint i=0; i<recipients.length; i++) {
payAndNotify(token, recipients[i], amounts[i], ref);
}
}
// Allows to make a payment from the sender to an address given an allowance to this contract
// Equivalent to xchf.transferAndCall(recipient, amountInBase)
function payAndNotify(address recipient, uint256 amountInBase, bytes calldata ref) external {
payAndNotify(IBrokerbot(recipient).base(), recipient, amountInBase, ref);
}
function payAndNotify(IERC20 token, address recipient, uint256 amount, bytes calldata ref) public {
require(IERC20(token).transferFrom(msg.sender, recipient, amount));
IBrokerbot(recipient).processIncoming(token, msg.sender, amount, ref);
}
function payFromEtherAndNotify(IBrokerbot recipient, uint256 amountInBase, bytes calldata ref) external payable {
IERC20 base = recipient.base();
// Check if the brokerbot has setting to keep ETH
if (hasSettingKeepEther(recipient)) {
uint256 priceInEther = getPriceInEtherFromOracle(amountInBase, base);
recipient.processIncoming{value: priceInEther}(base, msg.sender, amountInBase, ref);
// Pay back ETH that was overpaid
if (priceInEther < msg.value) {
(bool success, ) = msg.sender.call{value:msg.value - priceInEther}(""); // return change
require(success, "Transfer failed.");
}
} else {
payFromEther(address(recipient), amountInBase, base);
recipient.processIncoming(base, msg.sender, amountInBase, ref);
}
}
/***
* Pay from any ERC20 token (which has Uniswapv3 ERC20-ETH pool) and send swapped base currency to brokerbot.
* The needed amount needs to be approved at the ERC20 contract beforehand
*/
function payFromERC20AndNotify(address recipient, uint256 amountBase, address erc20, uint256 amountInMaximum, bytes memory path, bytes calldata ref) external {
IERC20 base = IBrokerbot(recipient).base();
uint256 balanceBefore = IERC20(base).balanceOf(recipient);
payFromERC20(amountBase, amountInMaximum, erc20, path, recipient);
uint256 balanceAfter = IERC20(base).balanceOf(recipient);
require(amountBase == (balanceAfter - balanceBefore), "swap error");
IBrokerbot(recipient).processIncoming(base, msg.sender, balanceAfter - balanceBefore, ref);
}
/**
* Checks if the recipient(brokerbot) has setting enabled to keep ether
*/
function hasSettingKeepEther(IBrokerbot recipient) public view returns (bool) {
return recipient.settings() & KEEP_ETHER == KEEP_ETHER;
}
/**
* In case tokens have been accidentally sent directly to this contract.
* Make sure to be fast as anyone can call this!
*/
function recover(IERC20 ercAddress, address to, uint256 amount) external {
require(ercAddress.transfer(to, amount));
}
// solhint-disable-next-line no-empty-blocks
receive() external payable {
// Important to receive ETH refund from Uniswap
}
}
{
"compilationTarget": {
"src/brokerbot/PaymentHub.sol": "PaymentHub"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"contract IQuoter","name":"_quoter","type":"address"},{"internalType":"contract ISwapRouter","name":"swapRouter","type":"address"},{"internalType":"contract AggregatorV3Interface","name":"_aggregatorCHFUSD","type":"address"},{"internalType":"contract AggregatorV3Interface","name":"_aggregatorETHUSD","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"erc20In","type":"address"}],"name":"approveERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getLatestPriceCHFUSD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLatestPriceETHUSD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountInBase","type":"uint256"},{"internalType":"bytes","name":"path","type":"bytes"}],"name":"getPriceInERC20","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountInBase","type":"uint256"},{"internalType":"contract IBrokerbot","name":"brokerBot","type":"address"}],"name":"getPriceInEther","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountInBase","type":"uint256"},{"internalType":"contract IERC20","name":"base","type":"address"}],"name":"getPriceInEtherFromOracle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IBrokerbot","name":"recipient","type":"address"}],"name":"hasSettingKeepEther","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"multiPay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"multiPayAndNotify","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountInBase","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"payAndNotify","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"payAndNotify","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"address","name":"erc20In","type":"address"},{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"}],"name":"payFromERC20","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountBase","type":"uint256"},{"internalType":"address","name":"erc20","type":"address"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"payFromERC20AndNotify","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountInBase","type":"uint256"},{"internalType":"contract IERC20","name":"base","type":"address"}],"name":"payFromEther","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IBrokerbot","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountInBase","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"payFromEtherAndNotify","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"ercAddress","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"recover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]