When you send ETH to a smart contract, you're actually calling its receive()
function (https://docs.soliditylang.org/en/v0.8.17/contracts.html#receive-ether-function). If that function contains logic, it gets executed upon sending ETH to the smart contract, so yes, making a contract forward ETH that is sent to it (or do anything with it, actually, including dividing it and sending it to multiple addresses) is perfectly possible. In that case, the user sending the ETH in the first place is the one paying for the gas (smart contracts can't pay gas on their own). Below is a very simple example contract that forwards any ETH sent to it to the address that deployed it, and reverts the transaction ( = sends the ETH back to the sender) if for some reason it can't.
contract Forwarder {
address owner;
constructor() {
owner = msg.sender;
}
receive() external payable {
(bool s,) = payable(owner).call{value: msg.value}(new bytes(0));
require(s);
}
}
Note that it works that way only for ETH ( when i say "ETH" i mean the native token of the chain you're on, so ETH on ethereum mainnet, but BNB on the BSC, MATIC on polygon, etc..). ERC20 tokens work differently, and you can't do that with them.
EDIT : As Safi explained, USDT (or any ERC20 tokens) transfer are different in nature than ETH transfers, and therefore can't be treated the same way. Basically, sending ETH calls your contract, and sending USDT doesnt. From there, you have two options. If you have a frontend people are going to use to send you USDT, you can do :
// The IERC20 interface, to allow your contract to interact with ERC20 contracts.
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
contract Forwarder {
address owner;
address USDTContract =....; // Well, the usdt contract address
constructor() {
owner = msg.sender;
}
// To receive ETH, people just need to send ETH to the address as they would do to any address, just clicking metamask's send button.
receive() external payable {
(bool s,) = payable(owner).call{value: msg.value}(new bytes(0));
require(s);
}
/* For USDT, the control flow is reversed, since an address sending ERC20 tokens to your contract
doesnt trigger any kind of execution, if we need to act upon receiving ERC20s,
we instead take the USDT from the users wallet and then do stuff with it.
In that case, the user would need to call this function, they can't do that by just using mm's send button. */
function transferUsdtToOwner(uint amount) external {
IERC20(USDTAddress).transferFrom(msg.sender, owner, amount);
}
}
Your other option is to have the users send USDT to your contract, and then transfer it yourself to your address (you can let the USDT accumulate inside the contract and just do it once in a while)
// User sends USDT to the contract and then you call that function
function withdrawUSDT() external {
IERC20(USDTAddress).transfer(owner, IERC20(USDTAddress).balanceOf(address(this));
}
If you really don't want to do it yourself, you could also incentivize external actors to do it for you, for example by giving them a small share of what's in there if they do :
function withdrawUSDT() external {
IERC20(USDTAddress).transfer(msg.sender, IERC20(USDTAddress).balanceOf(address(this)) / 100); // The caller would get 1%, you'd get the rest
IERC20(USDTAddress).transfer(owner, IERC20(USDTAddress).balanceOf(address(this)));
}