diff --git a/contracts/SwapHelperLib.sol b/contracts/SwapHelperLib.sol index ea4aed37..c3f7c3c8 100644 --- a/contracts/SwapHelperLib.sol +++ b/contracts/SwapHelperLib.sol @@ -123,4 +123,43 @@ library SwapHelperLib { ); return amounts[path.length - 1]; } + + function swapTokensForExactTokens( + address zetaToken, + address uniswapV2Factory, + address uniswapV2Router, + address zrc20, + uint256 amount, + address targetZRC20, + uint256 amountInMax + ) internal returns (uint256) { + bool existsPairPool = _existsPairPool( + uniswapV2Factory, + zrc20, + targetZRC20 + ); + + address[] memory path; + if (existsPairPool) { + path = new address[](2); + path[0] = zrc20; + path[1] = targetZRC20; + } else { + path = new address[](3); + path[0] = zrc20; + path[1] = zetaToken; + path[2] = targetZRC20; + } + + IZRC20(zrc20).approve(address(uniswapV2Router), amountInMax); + uint256[] memory amounts = IUniswapV2Router01(uniswapV2Router) + .swapTokensForExactTokens( + amount, + amountInMax, + path, + address(this), + block.timestamp + MAX_DEADLINE + ); + return amounts[0]; + } } diff --git a/helpers/balances.ts b/helpers/balances.ts index 2ef5677b..6b7c40c6 100644 --- a/helpers/balances.ts +++ b/helpers/balances.ts @@ -1,101 +1,153 @@ +import ERC20_ABI from "@openzeppelin/contracts/build/contracts/ERC20.json"; import { getEndpoints } from "@zetachain/networks/dist/src/getEndpoints"; -import networks from "@zetachain/networks/dist/src/networks"; import { getAddress } from "@zetachain/protocol-contracts"; -import ZetaEth from "@zetachain/protocol-contracts/abi/evm/Zeta.eth.sol/ZetaEth.json"; import ZRC20 from "@zetachain/protocol-contracts/abi/zevm/ZRC20.sol/ZRC20.json"; import { ethers } from "ethers"; -import { formatEther } from "ethers/lib/utils"; +import { formatUnits } from "ethers/lib/utils"; +import fetch from "isomorphic-fetch"; -const fetchBitcoinBalance = async (address: string) => { - const API = getEndpoints("esplora", "btc_testnet")[0].url; - if (API === undefined) throw new Error("fetchBitcoinBalance: API not found"); - - try { - const response = await fetch(`${API}/address/${address}`); - const data = await response.json(); - const { funded_txo_sum, spent_txo_sum } = data.chain_stats; - const balance = funded_txo_sum - spent_txo_sum; - return { - native: `${balance / 100000000}`, - networkName: "btc_testnet", - }; - } catch (error) {} +export const getForeignCoins = async () => { + const api = getEndpoints("cosmos-http", "zeta_testnet")[0]?.url; + const endpoint = `${api}/zeta-chain/zetacore/fungible/foreign_coins`; + const response = await fetch(endpoint); + const data = await response.json(); + return data.foreignCoins; }; -const fetchNativeBalance = async (address: string, provider: any) => { - const balance = await provider.getBalance(address); - return parseFloat(formatEther(balance)).toFixed(2); +export const getSupportedChains = async () => { + const api = getEndpoints("cosmos-http", "zeta_testnet")[0]?.url; + const endpoint = `${api}/zeta-chain/observer/supportedChains`; + const response = await fetch(endpoint); + const data = await response.json(); + return data.chains; }; -const fetchZetaBalance = async ( - address: string, - provider: any, - networkName: string -) => { - if (networkName === "zeta_testnet") return ""; - const zetaAddress = getAddress("zetaToken", networkName as any); - const contract = new ethers.Contract(zetaAddress, ZetaEth.abi, provider); - const balance = await contract.balanceOf(address); - return parseFloat(formatEther(balance)).toFixed(2); -}; - -const fetchBalances = async ( - address: string, - provider: any, - networkName: string -) => { - try { - const native = await fetchNativeBalance(address, provider); - const zeta = await fetchZetaBalance(address, provider, networkName); - const isZeta = networkName === "zeta_testnet"; - const zrc20 = isZeta ? { zrc20: await fetchZRC20Balance(address) } : {}; - /* eslint-disable */ - return { networkName, native, zeta, ...zrc20 }; - /* eslint-enable */ - } catch (error) {} -}; - -const fetchZRC20Balance = async (address: string) => { - const api = getEndpoints("evm", "zeta_testnet"); - if (api.length < 1) return; - const rpc = api[0].url; - const provider = new ethers.providers.JsonRpcProvider(rpc); - const promises = Object.keys(networks).map(async (networkName) => { - try { - const zrc20 = getAddress("zrc20", networkName); - const contract = new ethers.Contract(zrc20, ZRC20.abi, provider); - const balance = await contract.balanceOf(address); - const denom = networks[networkName].assets[0].symbol; - if (balance > 0) { - const b = parseFloat(formatEther(balance)).toFixed(2); - return `${b} ${denom}`; - } - } catch (error) {} +export const getBalances = async (evmAddress: any, btcAddress = null) => { + let tokens = []; + const foreignCoins = await getForeignCoins(); + const supportedChains = await getSupportedChains(); + foreignCoins.forEach((token: any) => { + if (token.coin_type === "Gas") { + tokens.push({ + chain_id: token.foreign_chain_id, + coin_type: token.coin_type, + decimals: token.decimals, + symbol: token.symbol, + zrc20: token.zrc20_contract_address, + }); + tokens.push({ + chain_id: 7001, + coin_type: "ZRC20", + contract: token.zrc20_contract_address, + decimals: token.decimals, + symbol: token.symbol, + }); + } else if (token.coin_type === "ERC20") { + tokens.push({ + chain_id: token.foreign_chain_id, + coin_type: "ERC20", + contract: token.asset, + decimals: token.decimals, + symbol: token.symbol, + zrc20: token.zrc20_contract_address, + }); + tokens.push({ + chain_id: 7001, + coin_type: "ZRC20", + contract: token.zrc20_contract_address, + decimals: token.decimals, + symbol: token.name, + }); + } }); - - const result = await Promise.all(promises); - - // tBTC ZRC-20 balance - const btcZRC20 = "0x65a45c57636f9BcCeD4fe193A602008578BcA90b"; // TODO: use getAddress("zrc20", "btc_testnet") when available - const contract = new ethers.Contract(btcZRC20, ZRC20.abi, provider); - const balance = (await contract.balanceOf(address)) / 100000000; - if (balance > 0) { - result.push(`${balance} tBTC`); - } - - return result.filter((item) => item !== undefined).join(", "); -}; - -export const getBalances = async (address, btc_address = null) => { - const balancePromises = Object.keys(networks).map((networkName) => { - const api = getEndpoints("evm", networkName as any); - if (api.length >= 1) { - const rpc = api[0].url; - const provider = new ethers.providers.JsonRpcProvider(rpc); - return fetchBalances(address, provider, networkName); + supportedChains.forEach((chain: any) => { + const contract = getAddress("zetaToken", chain.chain_name as any); + if (contract) { + tokens.push({ + chain_id: chain.chain_id, + coin_type: "ERC20", + contract, + decimals: 18, + symbol: "WZETA", + }); } }); - const balances = await Promise.all(balancePromises); - if (btc_address) balances.push(await fetchBitcoinBalance(btc_address)); - return balances.filter((balance) => balance != null); + tokens.push({ + chain_id: 7001, + coin_type: "Gas", + decimals: 18, + symbol: "ZETA", + }); + + tokens = tokens.map((token: any) => { + const ticker = token.symbol.split("-")[0]; + const chain_name = + token.chain_id === 7001 + ? "zeta_testnet" + : supportedChains.find((c: any) => c.chain_id === token.chain_id) + ?.chain_name; + return { + ...token, + chain_name, + id: `${token.chain_id + .toString() + .toLowerCase()}__${token.symbol.toLowerCase()}`, + ticker, + }; + }); + + const balances = await Promise.all( + tokens.map(async (token: any) => { + const isGas = token.coin_type === "Gas"; + const isBitcoin = token.chain_name === "btc_testnet"; + const isERC = token.coin_type === "ERC20"; + const isZRC = token.coin_type === "ZRC20"; + if (isGas && !isBitcoin) { + const rpc = getEndpoints("evm", token.chain_name)[0]?.url; + const provider = new ethers.providers.StaticJsonRpcProvider(rpc); + return provider.getBalance(evmAddress).then((balance) => { + return { ...token, balance: formatUnits(balance, token.decimals) }; + }); + } else if (isGas && isBitcoin && btcAddress) { + const API = getEndpoints("esplora", "btc_testnet")[0]?.url; + return fetch(`${API}/address/${btcAddress}`).then(async (response) => { + const r = await response.json(); + const { funded_txo_sum, spent_txo_sum } = r.chain_stats; + const balance = ( + (funded_txo_sum - spent_txo_sum) / + 100000000 + ).toString(); + return { ...token, balance }; + }); + } else if (isERC) { + const rpc = getEndpoints("evm", token.chain_name)[0]?.url; + const provider = new ethers.providers.StaticJsonRpcProvider(rpc); + const contract = new ethers.Contract( + token.contract, + ERC20_ABI.abi, + provider + ); + return contract.balanceOf(evmAddress).then((balance: any) => { + return { ...token, balance: formatUnits(balance, token.decimals) }; + }); + } else if (isZRC) { + const rpc = getEndpoints("evm", token.chain_name)[0]?.url; + const provider = new ethers.providers.StaticJsonRpcProvider(rpc); + const contract = new ethers.Contract( + token.contract, + ZRC20.abi, + provider + ); + return contract.balanceOf(evmAddress).then((balance: any) => { + return { + ...token, + balance: formatUnits(balance, token.decimals), + }; + }); + } else { + return Promise.resolve(token); + } + }) + ); + return balances; }; diff --git a/helpers/fees.ts b/helpers/fees.ts index 7203e937..ee466350 100644 --- a/helpers/fees.ts +++ b/helpers/fees.ts @@ -11,7 +11,7 @@ const formatTo18Decimals = (n: any) => parseFloat(formatEther(n)).toFixed(18); export const fetchZEVMFees = async (network: string) => { const url = getEndpoints("evm", "zeta_testnet")[0].url; - const provider = new ethers.providers.JsonRpcProvider(url); + const provider = new ethers.providers.StaticJsonRpcProvider(url); const btcZRC20 = "0x65a45c57636f9BcCeD4fe193A602008578BcA90b"; // TODO: use getAddress("zrc20", "btc_testnet") when available const zrc20Address = network === "btc_testnet" ? btcZRC20 : getAddress("zrc20", network as any); diff --git a/helpers/index.ts b/helpers/index.ts index 814a1416..b2f0b887 100644 --- a/helpers/index.ts +++ b/helpers/index.ts @@ -3,4 +3,5 @@ export * from "./fees"; export * from "./pools"; export * from "./prepare"; export * from "./sendZETA"; +export * from "./sendZRC20"; export * from "./tx"; diff --git a/helpers/pools.ts b/helpers/pools.ts index 1b1f67d9..dea160cb 100644 --- a/helpers/pools.ts +++ b/helpers/pools.ts @@ -12,13 +12,17 @@ export const getPools = async () => { const data = await response.json(); const rpc = getEndpoints("evm", "zeta_testnet")[0]?.url; - const provider = new ethers.providers.JsonRpcProvider(rpc); + const provider = new ethers.providers.StaticJsonRpcProvider(rpc); const uniswapV2FactoryAddress = getAddress( "uniswapv2Factory", "zeta_testnet" ); - const zetaTokenAddress = getAddress("zetaToken", "zeta_testnet"); + + const zetaTokenAddress = getAddress( + "zetaToken", + "zeta_testnet" + ).toLowerCase(); const UniswapV2FactoryContract = new ethers.Contract( uniswapV2FactoryAddress, @@ -26,28 +30,28 @@ export const getPools = async () => { provider ); - const poolPromises = data.foreignCoins.map(async (token: any) => { - const zrc20Address = token.zrc20_contract_address; - const pair = await UniswapV2FactoryContract.getPair( - zrc20Address, - zetaTokenAddress - ); - - let reservesZRC20 = "0"; - let reservesZETA = "0"; - - if (pair !== ethers.constants.AddressZero) { - const contract = new ethers.Contract(pair, UniswapV2Pair.abi, provider); - const reserves = await contract.getReserves(); - reservesZRC20 = ethers.utils.formatEther(reserves[0]); - reservesZETA = ethers.utils.formatEther(reserves[1]); - } - return { - ...token, + const totalPairs = await UniswapV2FactoryContract.allPairsLength(); + let pairs = []; + for (let i = 0; i < totalPairs; i++) { + pairs.push(await UniswapV2FactoryContract.allPairs(i)); + } + + const poolPromises = pairs.map(async (pair: any) => { + let pool = { pair, - reservesZETA, - reservesZRC20, - }; + t0: {}, + t1: {}, + } as any; + const pairContract = new ethers.Contract(pair, UniswapV2Pair.abi, provider); + + pool.t0.address = await pairContract.token0(); + pool.t1.address = await pairContract.token1(); + + const reserves = await pairContract.getReserves(); + pool.t0.reserve = reserves[0]; + pool.t1.reserve = reserves[1]; + + return pool; }); const pools = await Promise.all(poolPromises); diff --git a/helpers/sendZETA.ts b/helpers/sendZETA.ts index c29ecb2e..991755b2 100644 --- a/helpers/sendZETA.ts +++ b/helpers/sendZETA.ts @@ -2,7 +2,7 @@ import networks from "@zetachain/networks/dist/src/networks"; import { getAddress } from "@zetachain/protocol-contracts"; import ZetaEthContract from "@zetachain/protocol-contracts/abi/evm/Zeta.eth.sol/ZetaEth.json"; import ZetaConnectorEth from "@zetachain/protocol-contracts/abi/evm/ZetaConnector.eth.sol/ZetaConnectorEth.json"; -import ZetaConnectorZEVM from "@zetachain/protocol-contracts/abi/zevm/ConnectorZEVM.sol/ZetaConnectorZEVM.json"; +import ZetaConnectorZEVM from "@zetachain/protocol-contracts/abi/zevm/ZetaConnectorZEVM.sol/ZetaConnectorZEVM.json"; import { ethers } from "ethers"; export const sendZETA = async ( @@ -13,7 +13,8 @@ export const sendZETA = async ( recipient: string ) => { let connectorContract: any; - const destinationChainId = networks[destination]?.chain_id; + const destinationChainId = + networks[destination as keyof typeof networks]?.chain_id; if (!destinationChainId) { throw new Error("Invalid destination chain"); } diff --git a/helpers/sendZRC20.ts b/helpers/sendZRC20.ts new file mode 100644 index 00000000..52417a5c --- /dev/null +++ b/helpers/sendZRC20.ts @@ -0,0 +1,81 @@ +import ERC20_ABI from "@openzeppelin/contracts/build/contracts/ERC20.json"; +import { getEndpoints } from "@zetachain/networks"; +import { networks } from "@zetachain/networks"; +import { getAddress } from "@zetachain/protocol-contracts"; +import ZRC20 from "@zetachain/protocol-contracts/abi/zevm/ZRC20.sol/ZRC20.json"; +import { ethers } from "ethers"; +import fetch from "isomorphic-fetch"; + +export const sendZRC20 = async ( + signer: any, + amount: string, + network: string, + destination: string, + recipient: string, + token: string +) => { + let value; + try { + value = ethers.utils.parseEther(amount); + } catch (e) { + throw new Error( + `${value} is not a number and not a valid value for --amount, ${e}` + ); + } + + const API = getEndpoints("cosmos-http", "zeta_testnet")?.[0]?.url; + const response = await fetch( + `${API}/zeta-chain/zetacore/fungible/foreign_coins` + ); + const data = await response.json(); + const foreignCoins = data.foreignCoins; + const networkChainID = networks[network as keyof typeof networks]?.chain_id; + const foreignCoinsFiltered = foreignCoins.filter((coin: any) => { + return coin.foreign_chain_id === networkChainID.toString(); + }); + let tx; + if (network === "zeta_testnet") { + const ZRC20Address = getAddress("zrc20", destination as any); + const contract = new ethers.Contract(ZRC20Address, ZRC20.abi, signer); + const value = ethers.utils.parseUnits(amount, 8); + await (await contract.connect(signer).approve(ZRC20Address, value)).wait(); + const to = + destination === "btc_testnet" + ? ethers.utils.toUtf8Bytes(recipient) + : signer.address; + return await contract.connect(signer).withdraw(to, value); + } else if (destination === "zeta_testnet") { + const TSSAddress = getAddress("tss", network as any); + const zrc20 = foreignCoinsFiltered.find( + (coin: any) => coin.symbol.toLowerCase() === token.toLowerCase() + ); + if (zrc20.coin_type.toLocaleLowerCase() === "erc20") { + if (zrc20 === undefined) { + throw new Error( + `Token ${token} is not one of the available tokens to be deposited from ${network} to zeta_testnet` + ); + } + const erc20ContractAddress = zrc20.asset; + const erc20TokenContract = new ethers.Contract( + erc20ContractAddress, + ERC20_ABI.abi, + signer + ); + const balance = await erc20TokenContract.balanceOf(signer.address); + if (balance.lt(value)) { + throw new Error("Insufficient token balance."); + } + const approveTx = await erc20TokenContract.approve(TSSAddress, value); + await approveTx.wait(); + tx = await erc20TokenContract.transfer(TSSAddress, value); + } else if (zrc20.coin_type.toLocaleLowerCase() === "gas") { + tx = await signer.sendTransaction({ + to: TSSAddress, + value, + }); + } + return tx; + } else { + throw new Error("Either --network or --destination should be zeta_testnet"); + } +}; diff --git a/helpers/tx.ts b/helpers/tx.ts index f54872d0..1d90c85a 100644 --- a/helpers/tx.ts +++ b/helpers/tx.ts @@ -69,10 +69,12 @@ const fetchCCTXData = async ( let confirmed_on_destination = false; if (outbound_tx_hash) { const chainName = findByChainId(networks, parseInt(receiver_chainId)); - const rpc = getEndpoints("evm", chainName)[0]?.url; - const provider = new ethers.providers.JsonRpcProvider(rpc); - const confirmed = await provider.getTransaction(outbound_tx_hash); - confirmed_on_destination = confirmed !== null; + if (chainName) { + const rpc = getEndpoints("evm", chainName as any)[0]?.url; + const provider = new ethers.providers.JsonRpcProvider(rpc); + const confirmed = await provider.getTransaction(outbound_tx_hash); + confirmed_on_destination = confirmed !== null; + } } const tx = { confirmed_on_destination, @@ -229,10 +231,16 @@ export const trackCCTX = async ( clearInterval(intervalID); if (!allOutboundMined) { + emitter.emit("mined-fail", { + cctxs, + }); reject("CCTX aborted or reverted"); } else { + emitter.emit("mined-success", { + cctxs, + }); if (json) console.log(JSON.stringify(cctxs, null, 2)); - resolve(); + resolve(cctxs); } } }, 3000); diff --git a/package.json b/package.json index 4cf25f53..29519b61 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.2", "@types/chai": "^4.2.0", + "@types/isomorphic-fetch": "^0.0.38", "@types/mocha": ">=9.1.0", "@types/node": ">=12.0.0", "@types/ws": "^8.5.5", @@ -73,9 +74,9 @@ "@nomiclabs/hardhat-ethers": "^2.2.3", "@openzeppelin/contracts": "^4.9.2", "@uniswap/v2-periphery": "^1.1.0-beta.0", - "@zetachain/networks": "^2.4.3", "@zetachain/faucet-cli": "^4.0.1", - "@zetachain/protocol-contracts": "^2.1.0", + "@zetachain/networks": "^2.4.3", + "@zetachain/protocol-contracts": "^3.0.1", "axios": "^1.4.0", "bech32": "^2.0.0", "bip39": "^3.1.0", diff --git a/tasks/balances.ts b/tasks/balances.ts index 34b4edb4..a76afbaf 100644 --- a/tasks/balances.ts +++ b/tasks/balances.ts @@ -32,6 +32,40 @@ const balancesError = ` npx hardhat balances --address `; +const summarizeTokens = (tokens: any[]) => { + let table = {} as any; + tokens.forEach((token) => { + if (!table[token.chain_name]) { + table[token.chain_name] = {}; + } + const balance = parseFloat(token.balance).toFixed(2); + if (parseFloat(token.balance) > 0) { + if (token.coin_type === "Gas") { + table[token.chain_name].gas = balance; + } else if (token.symbol === "ZETA") { + table[token.chain_name].zeta = balance; + } else if (token.coin_type === "ERC20") { + table[token.chain_name].erc20 = + (table[token.chain_name].erc20 + ? table[token.chain_name].erc20 + " " + : "") + + balance + + " " + + token.symbol; + } else if (token.coin_type === "ZRC20") { + table[token.chain_name].zrc20 = + (table[token.chain_name].zrc20 + ? table[token.chain_name].zrc20 + " " + : "") + + balance + + " " + + token.symbol; + } + } + }); + return table; +}; + const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const spinner = ora("Fetching balances..."); if (!args.json) { @@ -40,7 +74,7 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const { ethers, config } = hre as any; const pk = process.env.PRIVATE_KEY; let address: string; - let btc_address: string; + let btc_address: any; if (args.address) { address = args.address; @@ -52,24 +86,16 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { console.error(walletError + balancesError); return process.exit(1); } - - const filteredBalances = await getBalances(address, btc_address); + const balances = (await getBalances(address, btc_address)) as any; if (args.json) { - console.log(JSON.stringify(filteredBalances, null, 2)); + console.log(JSON.stringify(balances, null, 2)); } else { spinner.stop(); console.log(` EVM: ${address} ${btc_address ? `\nBitcoin: ${btc_address}` : ""} - `); - - const output = filteredBalances.reduce((acc: any, item: any) => { - const { networkName, ...rest } = item; - acc[networkName] = rest; - return acc; - }, {}); - - console.table(output); + `); + console.table(summarizeTokens(balances)); } }; diff --git a/tasks/pools.ts b/tasks/pools.ts index 855bd6ca..9fa80bb5 100644 --- a/tasks/pools.ts +++ b/tasks/pools.ts @@ -1,29 +1,61 @@ +import { getAddress } from "@zetachain/protocol-contracts"; +import { formatUnits } from "ethers/lib/utils"; import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { getForeignCoins } from "../helpers/balances"; import { getPools } from "../helpers/pools"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const foreignCoins = await getForeignCoins(); const pools = await getPools(); - const poolsFiltered = pools - .map((n: any) => ({ - Contract: n.pair, - ZETA: parseFloat(n.reservesZETA).toFixed(2), - "ZRC-20": parseFloat(n.reservesZRC20).toFixed(2), - name: n.asset ? n.name : n.symbol, - })) - .sort((a: any, b: any) => { - if (a.name > b.name) return -1; - }); + + const addressToInfo = foreignCoins.reduce((acc: any, coin: any) => { + acc[coin.zrc20_contract_address.toLowerCase()] = { + decimals: coin.decimals, + symbol: coin.symbol, + }; + return acc; + }, {}); + + const wzeta = getAddress("zetaToken", "zeta_testnet"); + const WZETA_ADDRESS = wzeta.toLowerCase(); + addressToInfo[WZETA_ADDRESS] = { decimals: 18, symbol: "WZETA" }; + + const poolsWithSymbolsAndDecimals = pools.map((pool) => { + pool.t0.reserve = formatUnits(pool.t0.reserve, pool.t0.decimals); + pool.t1.reserve = formatUnits(pool.t1.reserve, pool.t1.decimals); + const t0Info = addressToInfo[pool.t0.address.toLowerCase()] || { + decimals: 18, + symbol: "Unknown", + }; + const t1Info = addressToInfo[pool.t1.address.toLowerCase()] || { + decimals: 18, + symbol: "Unknown", + }; + + return { + ...pool, + t0: { ...pool.t0, ...t0Info }, + t1: { ...pool.t1, ...t1Info }, + }; + }); + + const tableData = {} as any; + poolsWithSymbolsAndDecimals.forEach((pool) => { + const r0 = parseFloat(pool.t0.reserve).toFixed(2); + const r1 = parseFloat(pool.t1.reserve).toFixed(2); + + tableData[pool.pair] = { + Pool: `${pool.t0.symbol} / ${pool.t1.symbol}`, + Reserves: `${r0} / ${r1}`, + }; + }); + if (args.json) { - console.log(JSON.stringify(poolsFiltered, null, 2)); + console.log(poolsWithSymbolsAndDecimals); } else { - const output = poolsFiltered.reduce((acc: any, item: any) => { - const { name, ...rest } = item; - acc[name] = rest; - return acc; - }, {}); - console.table(output); + console.table(tableData); } }; diff --git a/tasks/sendZRC20.ts b/tasks/sendZRC20.ts index a3525e68..a1c9ee87 100644 --- a/tasks/sendZRC20.ts +++ b/tasks/sendZRC20.ts @@ -1,36 +1,21 @@ -import { getAddress } from "@zetachain/protocol-contracts"; -import ZRC20 from "@zetachain/protocol-contracts/abi/zevm/ZRC20.sol/ZRC20.json"; import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { sendZRC20 } from "../helpers/sendZRC20"; + declare const hre: any; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const { ethers } = hre as any; const [signer] = await ethers.getSigners(); - let amount; - try { - amount = ethers.utils.parseEther(args.amount); - } catch (e) { - throw new Error( - `${args.amount} is not a number and not a valid value for --amount, ${e}` - ); - } - let tx; - if (hre.network.name === "zeta_testnet") { - const ZRC20Address = getAddress("zrc20", args.destination); - const contract = new ethers.Contract(ZRC20Address, ZRC20.abi, signer); - await (await contract.connect(signer).approve(ZRC20Address, amount)).wait(); - tx = await contract.connect(signer).withdraw(signer.address, amount); - } else if (args.destination === "zeta_testnet") { - const TSSAddress = getAddress("tss", hre.network.name); - tx = await signer.sendTransaction({ - to: TSSAddress, - value: amount, - }); - } else { - throw new Error("Either --network or --destination should be zeta_testnet"); - } + const tx = (await sendZRC20( + signer, + args.amount, + hre.network.name, + args.destination, + args.recipient, + args.token + )) as any; console.log(`Transaction hash: ${tx.hash}`); }; @@ -40,4 +25,6 @@ export const sendZRC20Task = task( main ) .addParam("amount", "Amount of ZRC-20 to send") - .addParam("destination", "Destination chain"); + .addParam("destination", "Destination chain") + .addParam("recipient", "Recipient address") + .addParam("token", "Token to send (geth, usdc, etc)"); diff --git a/templates/omnichain/tasks/interact.ts.hbs b/templates/omnichain/tasks/interact.ts.hbs index 73324787..70179010 100644 --- a/templates/omnichain/tasks/interact.ts.hbs +++ b/templates/omnichain/tasks/interact.ts.hbs @@ -1,8 +1,11 @@ import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { parseEther } from "@ethersproject/units"; +import { parseUnits } from "@ethersproject/units"; import { getAddress } from "@zetachain/protocol-contracts"; +import ERC20Custody from "@zetachain/protocol-contracts/abi/evm/ERC20Custody.sol/ERC20Custody.json"; import { prepareData } from "@zetachain/toolkit/helpers"; +import { utils, ethers } from "ethers"; +import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20.json"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const [signer] = await hre.ethers.getSigners(); @@ -12,10 +15,29 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { [{{#each arguments.types}}"{{this}}", {{/each}}], [{{#each arguments.names}}args.{{this}}, {{/each}}] ); - const to = getAddress("tss", hre.network.name); - const value = parseEther(args.amount); - const tx = await signer.sendTransaction({ data, to, value }); + let tx; + + if (args.token) { + const custodyAddress = getAddress("erc20Custody", hre.network.name as any); + const custodyContract = new ethers.Contract( + custodyAddress, + ERC20Custody.abi, + signer + ); + const tokenContract = new ethers.Contract(args.token, ERC20.abi, signer); + const decimals = await tokenContract.decimals(); + const value = parseUnits(args.amount, decimals); + const approve = await tokenContract.approve(custodyAddress, value); + await approve.wait(); + + tx = await custodyContract.deposit(signer.address, args.token, value, data); + tx.wait(); + } else { + const value = parseUnits(args.amount, 18); + const to = getAddress("tss", hre.network.name as any); + tx = await signer.sendTransaction({ data, to, value }); + } if (args.json) { console.log(JSON.stringify(tx, null, 2)); @@ -24,13 +46,14 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { console.log(`🚀 Successfully broadcasted a token transfer transaction on ${hre.network.name} network. 📝 Transaction hash: ${tx.hash} -`); + `); } }; task("interact", "Interact with the contract", main) .addParam("contract", "The address of the withdraw contract on ZetaChain") .addParam("amount", "Amount of tokens to send") + .addOptionalParam("token", "The address of the token to send") .addFlag("json", "Output in JSON") {{#each arguments.names}} .addParam("{{this}}") diff --git a/typechain-types/factories/contracts/SwapHelperLib__factory.ts b/typechain-types/factories/contracts/SwapHelperLib__factory.ts index 1d3b7e45..9b59195f 100644 --- a/typechain-types/factories/contracts/SwapHelperLib__factory.ts +++ b/typechain-types/factories/contracts/SwapHelperLib__factory.ts @@ -62,7 +62,7 @@ const _abi = [ ] as const; const _bytecode = - "0x610492610053600b82828239805160001a607314610046577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c8063c63585cc1461003a575b600080fd5b610054600480360381019061004f919061020d565b61006a565b6040516100619190610351565b60405180910390f35b600080600061007985856100dc565b915091508582826040516020016100919291906102e3565b604051602081830303815290604052805190602001206040516020016100b892919061030f565b6040516020818303038152906040528051906020012060001c925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415610145576040517fcb1e7cfe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161061017f578284610182565b83835b8092508193505050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156101f1576040517f78b507da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9250929050565b60008135905061020781610445565b92915050565b600080600060608486031215610226576102256103e1565b5b6000610234868287016101f8565b9350506020610245868287016101f8565b9250506040610256868287016101f8565b9150509250925092565b61026981610377565b82525050565b61028061027b82610377565b6103b3565b82525050565b61029761029282610389565b6103c5565b82525050565b60006102aa60208361036c565b91506102b5826103f3565b602082019050919050565b60006102cd60018361036c565b91506102d88261041c565b600182019050919050565b60006102ef828561026f565b6014820191506102ff828461026f565b6014820191508190509392505050565b600061031a826102c0565b9150610326828561026f565b6014820191506103368284610286565b6020820191506103458261029d565b91508190509392505050565b60006020820190506103666000830184610260565b92915050565b600081905092915050565b600061038282610393565b9050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103be826103cf565b9050919050565b6000819050919050565b60006103da826103e6565b9050919050565b600080fd5b60008160601b9050919050565b7f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f600082015250565b7fff00000000000000000000000000000000000000000000000000000000000000600082015250565b61044e81610377565b811461045957600080fd5b5056fea2646970667358221220827821eff9c6c7e0b37f2bc10841b7e77efac4f01f5290fd4ae69062e24e2bfd64736f6c63430008070033"; + "0x610492610053600b82828239805160001a607314610046577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c8063c63585cc1461003a575b600080fd5b610054600480360381019061004f919061020d565b61006a565b6040516100619190610351565b60405180910390f35b600080600061007985856100dc565b915091508582826040516020016100919291906102e3565b604051602081830303815290604052805190602001206040516020016100b892919061030f565b6040516020818303038152906040528051906020012060001c925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415610145576040517fcb1e7cfe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161061017f578284610182565b83835b8092508193505050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156101f1576040517f78b507da00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9250929050565b60008135905061020781610445565b92915050565b600080600060608486031215610226576102256103e1565b5b6000610234868287016101f8565b9350506020610245868287016101f8565b9250506040610256868287016101f8565b9150509250925092565b61026981610377565b82525050565b61028061027b82610377565b6103b3565b82525050565b61029761029282610389565b6103c5565b82525050565b60006102aa60208361036c565b91506102b5826103f3565b602082019050919050565b60006102cd60018361036c565b91506102d88261041c565b600182019050919050565b60006102ef828561026f565b6014820191506102ff828461026f565b6014820191508190509392505050565b600061031a826102c0565b9150610326828561026f565b6014820191506103368284610286565b6020820191506103458261029d565b91508190509392505050565b60006020820190506103666000830184610260565b92915050565b600081905092915050565b600061038282610393565b9050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103be826103cf565b9050919050565b6000819050919050565b60006103da826103e6565b9050919050565b600080fd5b60008160601b9050919050565b7f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f600082015250565b7fff00000000000000000000000000000000000000000000000000000000000000600082015250565b61044e81610377565b811461045957600080fd5b5056fea2646970667358221220b10a491e33dfa621fcb8a77d7febf000e330b5776d1d0edf68ea15b6eb7590ad64736f6c63430008070033"; type SwapHelperLibConstructorParams = | [signer?: Signer] diff --git a/yarn.lock b/yarn.lock index fdd7ac0a..85b186f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1566,6 +1566,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/isomorphic-fetch@^0.0.38": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.38.tgz#90882932a371e294a3c6143430275c15cb6ebf1b" + integrity sha512-DcXWx26qmt89lQ1Saoit0P38yHqT7fGceVs7UpMy2gU7AjEZISPdM7rqoFY5VHtl33H+cJ+VBDYmNRssTM4Zlw== + "@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" @@ -1802,10 +1807,10 @@ dependencies: dotenv "^16.1.4" -"@zetachain/protocol-contracts@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@zetachain/protocol-contracts/-/protocol-contracts-2.1.0.tgz#775b4eee7c85d115232dece121cbfc798fde6b63" - integrity sha512-xmG6p8DizIk0h7Tr8Lt6UMG0ejrfRrPx0qH9ze3enwblo7W+eGP12oQ7djanYu50B0pNhh9z7xy/IYxKa9wD0Q== +"@zetachain/protocol-contracts@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@zetachain/protocol-contracts/-/protocol-contracts-3.0.1.tgz#fefbc93dfea7d50193831b541fd8b847f3db57c7" + integrity sha512-F0ZY3z25TftUFHWjNP63gPh1ppFcTaATAqDX87Ld9ED2iDQ7tUEAN7R2A8BEzbcIi+n8dfLWQSPCFcgP1vFB/w== abbrev@1: version "1.1.1"