zeta-chain / example-contracts

Examples of universal smart contract implementing cross-chain swaps, NFT transfers, ERC-20 transfers and more
https://www.zetachain.com/docs
MIT License
82 stars 47 forks source link

Update "Swap to ZETA" tutorial to "Swap to Any Token" #180

Closed fadeev closed 3 months ago

fadeev commented 5 months ago

Right now we have https://www.zetachain.com/docs/developers/tutorials/swap-zeta/

Update this tutorial so that it allows swapping without withdrawing, that is swapping any token from any connected chain to a ZRC-20 and keeping the ZRC-20 on ZetaChain (no withdrawal). This is useful, because sometimes getting ZRC-20 on ZetaChain is the goal, maybe you want to then use it in another dapp.

This proposed example will replace SwapToZeta.

Deliverables

fadeev commented 5 months ago

The idea is for the interact task to have a withdraw flag which is by default true. Which means by default the token you're swapping to will be withdrawn to the destination chain.

npx hardhat interact --network sepolia_testnet --contract ... --recipient ... --amount 0.1 --target-token 0x65a45c57636f9BcCeD4fe193A602008578BcA90b

You can set it to false explicitly, which means if the target token is a ZRC-20, after swapping the ZRC-20 will be transferred to recipient on ZetaChain.

npx hardhat interact --network sepolia_testnet --contract ... --recipient ... --amount 0.1 --target-token 0x65a45c57636f9BcCeD4fe193A602008578BcA90b --withdraw false

Under the hood I was thinking about something long these lines:

        bool withdraw = false;

        if (context.chainID == BITCOIN) {
            target = BytesHelperLib.bytesToAddress(message, 0);
            to = abi.encodePacked(BytesHelperLib.bytesToAddress(message, 20));

            if (message.length >= 41) {
                withdraw = bytesToBool(message, 40);
            }
        } else {
            (
                address targetToken,
                bytes memory recipient,
                bool withdrawFlag
            ) = abi.decode(message, (address, bytes, bool));
            target = targetToken;
            to = recipient;
            withdraw = withdrawFlag;
        }

withdraw is false by default, but if it's supplied as the last value in the message, it can be set to true.

fadeev commented 5 months ago

ZETA token no longer needs to be handled separately as it's just one case of a non-withdrawable token.

        if (withdraw) {
            (gasZRC20, gasFee) = IZRC20(target).withdrawGasFee();

            inputForGas = SwapHelperLib.swapTokensForExactTokens(
                systemContract,
                zrc20,
                gasFee,
                gasZRC20,
                amount
            );
        }

        uint256 outputAmount = SwapHelperLib.swapExactTokensForTokens(
            systemContract,
            zrc20,
            withdraw ? amount - inputForGas : amount,
            target,
            0
        );

        if (!withdraw) {
            IWETH9(target).transfer(
                address(uint160(bytes20(to))),
                outputAmount
            );
        } else {
            IZRC20(gasZRC20).approve(target, gasFee);
            IZRC20(target).withdraw(to, outputAmount);
        }

User must set withdraw to false explicitly to swap to ZETA. I think it's fine.

fadeev commented 5 months ago

btw the code above is just for demo purposes, it probably doesn't work.

fadeev commented 5 months ago

Ideally, this doesn't require major changes to the source. I was thinking in terms of LOC and complexity it could be comparable to the existing Swap to ZETA. If not, we may need to re-evaluate.