> USE THIS AT YOUR OWN RISK!!
USDC on the following networks are currently supported as proof of concept:
This app has the following cross-chain functinality:
And the ZetaChain functionalities:
On ZetaChain EVM the ZRC20 contract represents a fungible token on a foregin blockchain (could be gas, ERC20, SPL, or BTC). Users can deposit their fungible tokens to a Gateway contract and get the ZRC20 version of it on ZetaChain, or withdraw their ZRC20 balance to the native chain to an native address. See the implementation.
As a result, the same assets such as USDC issued on different networks, once depsoited into ZetaChain EVM, will be accounted in different ZRC20 contracts, i.e. USDC.ARB, USDC.BASE, USDC.SOL, USDC.AVAX. This ZetaChain node API returns all the foreign coins represented as ZRC20 contracts on zEVM.
This fragmented representations of the same assets (USDC) coming from different chains complicates the DeFi infrastructure on ZetaChain, resulting in many more Uniswap or other AMM pools than necessary. To unify the same assets from different chains, we deploy a CurveStableSwapNG pool on zEVM for USDC that consists of the 4 ZRC20 (USDC.ARB/USDC.BASE/USDC.SOL/USDC.AVAX). User puts USDC.X into the pool and get USDC.4 as Liquidity provider token (represents shares of the pool) USDC.4 (ERC20). User can redeem the USDC.4 to any USDC.X, and can optionally withdraw them to chain X using the withdraw() function of the USDC.X ZRC20 contract.
CurveStableSwapNG
workOn ZetaChain EVM a CurveStableSwapNG
pool was created with the following 4 ZRC20
coins:
| Name | Contract Type | Address |
|——–|——|———|
| AMM Implementation |CurveStableSwapNG impl | 0x1eD4644Bd2D0e1BBd89d100ba96B1dA48Bf1048f
|
| Factory | CurveStableSwapFactoryNG | 0x4dA267b2F80c74D0FdBcF06f4F65730bB003223E
|
| Pool/USDC.4 | ERC20/CurveStableSwapNG | 0xCA4b0396064F40640F1d9014257a99aB3336C724
|
| USDC.ARB | ZRC20 | 0x0327f0660525b15Cdb8f1f5FBF0dD7Cd5Ba182aD
|
| USDC.SOL | ZRC20 |0x8344d6f84d26f998fa070BbEa6D2E15E359e2641
|
| USDC.BASE | ZRC20 |0x96152E6180E085FA57c7708e18AF8F05e37B479D
|
| USDC.AVAX | ZRC20 |0xa52Ad01A1d62b408fFe06C2467439251da61E4a9
|
|——–|——|———|
| USDC4Withdrawer | CCTX IO | 0x3aEA86Ec4ce409C7e01bD4f5D6A175cf9BC7c140
|
| USDC4Deposit | CCTX IO | 0x5D6750637FF2776F9d9B51f11B2eB90D1419A60F
|
You can inspect and interact with these contracts on zetachain blockscout explorer: https://zetachain.blockscout.com
The CurveStableSwapNG
contract is both a curve pool and also a
ERC20 token, (USDC.4, the liquidity provider token).
Since USDC.4 is just the LP token of the CurveStableSwapNG
pool,
to convert a USDC.X to USDC.4, one simply call one of the add_liquidity
functions in the ``CurveStableSwapNG` pool:
def add_liquidity(
_amounts: DynArray[uint256, MAX_COINS],
_min_mint_amount: uint256,
_receiver: address = msg.sender
) -> uint256:
To do the inverse, i.e. converting the unified USDC.4 balance into
USDC.Y (the Y need not be the same X that got added into liquidity pool to get the USDC.4), one just burns the USDC.4 balance by removing liquidity from the
CurveStableSwapNG
pool:
def remove_liquidity_one_coin(
_burn_amount: uint256,
i: int128,
_min_received: uint256,
_receiver: address = msg.sender,
) -> uint256:
One combines the USDC.4->USDC.X with the withdraw()
(explained in following sections) of USDC.X in a contract.
USDC.4 -> (remove_liquidity) -> USDC.X -> (ZRC20 withdraw) -> USDC on chain X
One deposit USDC on chain X with depositAndCall (details to be explained in following sections )
USDC on chain X -> (call depositAndCall on chain X Gateway contract) -> (ZRC20 USDC.X, and add it as liquidity to the CurveStableSwapNG
pool) -> USDC.4 on ZetaChain
Compose the above two:
USDC on chain X => USDC.4 on ZetaChain => USDC on chain Y
ZRC20 contract has a funciton function
withdraw(
bytes memory to,
uint256 amount
)
which will burn the amount
of the caller balance in this ZRC20 contract,
and release the fungible token this ZRC20 represents to the to
address
on an external chain.
On a supported foreign chain call the Gateway contract function (say Arbitrum Gateway the following functions:
// deposit ETH into ZetaChain
function deposit(
address receiver,
RevertOptions calldata revertOptions
) external payable whenNotPaused {}
// deposit ERC20 asset into ZetaChain
function deposit(
address receiver,
uint256 amount,
address asset,
RevertOptions calldata revertOptions
) external whenNotPaused {
The transaciton will be observeed by ZetaChain observers and corresponding
ZRC20 will be minted to the receiver
on ZetaChain.
There is a variant of deposit()
function that allows the deposit into ZRC20
balance, and immediately call a user specified contract on zEVM in the same
transction:
function depositAndCall(
address receiver,
bytes calldata payload,
RevertOptions calldata revertOptions
) external payable whenNotPaused {}
function depositAndCall(
address receiver,
uint256 amount,
address asset,
bytes calldata payload,
RevertOptions calldata revertOptions
) external whenNotPaused {}
The additional parameter payload
bytes is the contract call after Deposit
into ZRC20 happened. The receiver
should be a contract on ZetaChain EVM
which will be called with CALLDATA payload
bytes.