diff --git a/app/bridge/bridging.tsx b/app/bridge/bridging.tsx index eee05081..4de92311 100644 --- a/app/bridge/bridging.tsx +++ b/app/bridge/bridging.tsx @@ -23,6 +23,7 @@ import { GetWalletClientResult } from "wagmi/actions"; import { maxBridgeAmountInUnderlying } from "@/hooks/bridge/helpers/amounts"; import { BaseNetwork } from "@/config/interfaces"; import { validateInputTokenAmount } from "@/utils/validation.utils"; +import { ETHEREUM_VIA_GRAVITY_BRIDGE } from "@/config/networks"; interface BridgeProps { hook: BridgeHookReturn; @@ -103,6 +104,12 @@ const Bridging = (props: BridgeProps) => { isCosmosNetwork(props.hook.selections.toNetwork) ? { cosmosAddress: { + addressName: + props.hook.selections.toNetwork.id === + ETHEREUM_VIA_GRAVITY_BRIDGE.id + ? "Gravity Bridge" + : undefined, + chainId: props.hook.selections.toNetwork.chainId, addressPrefix: props.hook.selections.toNetwork.addressPrefix, currentAddress: props.hook.addresses.getReceiver() ?? "", setAddress: (address: string) => @@ -145,10 +152,6 @@ const Bridging = (props: BridgeProps) => { addresses={{ from: props.hook.addresses.getSender(), to: props.hook.addresses.getReceiver(), - name: - props.hook.direction === "in" - ? networkName(props.hook.selections.fromNetwork) - : networkName(props.hook.selections.toNetwork), }} fromNetwork={networkName(props.hook.selections.fromNetwork)} toNetwork={networkName(props.hook.selections.toNetwork)} @@ -170,7 +173,7 @@ const Bridging = (props: BridgeProps) => { }} extraDetails={ props.hook.selections.toNetwork?.id === - "ethereum-via-gravity-bridge" ? ( + ETHEREUM_VIA_GRAVITY_BRIDGE.id ? ( To bridge your tokens to Ethereum through Gravity Bridge, first ensure that you have an IBC wallet like Keplr. @@ -346,22 +349,29 @@ const Bridging = (props: BridgeProps) => { ? props.hook.selections.token.symbol : props.hook.selections.token.name, } - : ({ + : { name: "Select Token", icon: "loader.svg", id: "", - } as Item) + } } - items={ - props.hook.allOptions.tokens.map((token) => ({ + items={props.hook.allOptions.tokens + .map((token) => ({ ...token, name: token.name.length > 24 ? token.symbol : token.name, secondary: displayAmount( token.balance ?? "0", token.decimals ), - })) ?? [] - } + })) + .sort((a, b) => { + if (Number(a.secondary) === Number(b.secondary)) { + return b.name.toLowerCase() > a.name.toLowerCase() + ? -1 + : 1; + } + return Number(a.secondary) > Number(b.secondary) ? -1 : 1; + })} onChange={(tokenId) => props.hook.setState("token", tokenId)} /> diff --git a/app/bridge/components/confirmationModal.tsx b/app/bridge/components/confirmationModal.tsx index 3928ea19..81aed785 100644 --- a/app/bridge/components/confirmationModal.tsx +++ b/app/bridge/components/confirmationModal.tsx @@ -6,13 +6,15 @@ import Image from "next/image"; import React, { ReactNode } from "react"; import styles from "../bridge.module.scss"; import PopUp from "@/components/popup/popup"; +import { connectToKeplr } from "@/utils/keplr/connectKeplr"; +import { formatError } from "@/utils/formatting.utils"; +import InfoPop from "@/components/infopop/infopop"; interface Props { imgUrl: string; addresses: { from: string | null; to: string | null; - name: string | null; }; token?: { name: string; @@ -27,6 +29,8 @@ interface Props { onConfirm: () => void; }; cosmosAddress?: { + addressName?: string; // for eth via gravity bridge + chainId: string; addressPrefix: string; currentAddress: string; setAddress: (address: string) => void; @@ -34,6 +38,8 @@ interface Props { extraDetails?: ReactNode; } const ConfirmationModal = (props: Props) => { + console.log(props); + const [keplrError, setKeplrError] = React.useState(""); return (
@@ -43,8 +49,11 @@ const ConfirmationModal = (props: Props) => { {"props"} - Bridge {props.token?.name} {props.type}{" "} - {props.type == "in" ? "from" : "to"} {props.addresses?.name} + {`Bridge ${props.token?.name} ${props.type} ${ + props.type === "in" + ? "from " + props.fromNetwork + : "to " + props.toNetwork + }`} {props.extraDetails && ( { content={{props.addresses.to}} > - {props.toNetwork + " : "} + {(props.cosmosAddress?.addressName ?? props.toNetwork) + + " : "} {props.addresses.to ? props.addresses.to?.slice(0, 6) + "..." + @@ -118,7 +128,7 @@ const ConfirmationModal = (props: Props) => { ) : ( - {props.toNetwork + " : "} + {(props.cosmosAddress?.addressName ?? props.toNetwork) + " : "} {props.addresses.to ? props.addresses.to?.slice(0, 6) + "..." + @@ -144,9 +154,57 @@ const ConfirmationModal = (props: Props) => { onChange={function (e: React.ChangeEvent): void { props.cosmosAddress?.setAddress(e.target.value); }} + height={"md"} /> + )} + diff --git a/app/lending/utils.tsx b/app/lending/utils.tsx index 5106822b..0d1b1022 100644 --- a/app/lending/utils.tsx +++ b/app/lending/utils.tsx @@ -33,10 +33,15 @@ interface LendingComboReturn { }; }; transaction: { - performTx: (amount: string, txType: CTokenLendingTxTypes) => void; + performTx: ( + amount: string, + txType: CTokenLendingTxTypes, + max: boolean + ) => void; validateParams: ( amount: string, - txType: CTokenLendingTxTypes + txType: CTokenLendingTxTypes, + max: boolean ) => ValidationReturn; }; selection: { @@ -121,7 +126,11 @@ export function useLendingCombo(props: LendingComboProps): LendingComboReturn { }, [chainId, cNote?.underlying.address]); // transaction functions - function lendingTx(amount: string, txType: CTokenLendingTxTypes) { + function lendingTx( + amount: string, + txType: CTokenLendingTxTypes, + max: boolean + ) { if (!selection.selectedCToken || !signer) return; const { data, error } = transaction.createNewLendingFlow({ chainId: signer.chain.id, @@ -129,6 +138,7 @@ export function useLendingCombo(props: LendingComboProps): LendingComboReturn { cToken: selection.selectedCToken, amount, txType, + max, }); if (error) { console.log(error); @@ -142,7 +152,8 @@ export function useLendingCombo(props: LendingComboProps): LendingComboReturn { } const validateParams = ( amount: string, - txType: CTokenLendingTxTypes + txType: CTokenLendingTxTypes, + max: boolean ): ValidationReturn => { if (!selection.selectedCToken || !signer) return { isValid: false }; return transaction.validateParams({ @@ -151,6 +162,7 @@ export function useLendingCombo(props: LendingComboProps): LendingComboReturn { cToken: selection.selectedCToken, amount, txType, + max, }); }; diff --git a/app/lp/components/ambientLPModal.tsx b/app/lp/components/ambientLPModal.tsx index b96586b2..428a867c 100644 --- a/app/lp/components/ambientLPModal.tsx +++ b/app/lp/components/ambientLPModal.tsx @@ -12,7 +12,7 @@ import styles from "./cantoDex.module.scss"; import Amount from "@/components/amount/amount"; import Tabs from "@/components/tabs/tabs"; import { ModalItem } from "@/app/lending/components/modal/modal"; -import { greaterThan, percentOfAmount } from "@/utils/tokens/tokenMath.utils"; +import { percentOfAmount } from "@/utils/tokens/tokenMath.utils"; import Slider from "@/components/slider/slider"; import clsx from "clsx"; import PopUp from "@/components/popup/popup"; diff --git a/components/amount/amount.tsx b/components/amount/amount.tsx index 08a03159..8515d5e3 100644 --- a/components/amount/amount.tsx +++ b/components/amount/amount.tsx @@ -10,7 +10,7 @@ interface Props { IconUrl: string; title: string; symbol: string; - onChange: (e: React.ChangeEvent) => void; + onChange: (e: React.ChangeEvent, max: boolean) => void; decimals: number; value: string; max: string; @@ -102,13 +102,16 @@ const Amount = (props: Props) => { { - props.onChange({ - target: { - value: formatBalance(props.max, props.decimals, { - precision: props.decimals, - }), - }, - } as any); + props.onChange( + { + target: { + value: formatBalance(props.max, props.decimals, { + precision: props.decimals, + }), + }, + } as any, + true + ); }} > @@ -124,7 +127,7 @@ const Amount = (props: Props) => { onChange={(e) => { e.target.value = decommify(e.target.value); if (e.target.value === "" || e.target.value.match(/^\d*\.?\d*$/)) { - props.onChange(e); + props.onChange(e, false); } }} className={styles.input} diff --git a/hooks/bridge/transactions/keplr/ibcKeplr.ts b/hooks/bridge/transactions/keplr/ibcKeplr.ts index d6825457..e6f3bc99 100644 --- a/hooks/bridge/transactions/keplr/ibcKeplr.ts +++ b/hooks/bridge/transactions/keplr/ibcKeplr.ts @@ -80,7 +80,7 @@ export async function ibcInKeplr( } // check if we can obtain the keplr client const { data: keplrClient, error: clientError } = await connectToKeplr( - cosmosNetwork + cosmosNetwork.chainId ); if (clientError) { return NEW_ERROR("ibcInKeplr::" + clientError.message); diff --git a/hooks/lending/interfaces/lendingTxTypes.ts b/hooks/lending/interfaces/lendingTxTypes.ts index 01baa31c..3ad8090d 100644 --- a/hooks/lending/interfaces/lendingTxTypes.ts +++ b/hooks/lending/interfaces/lendingTxTypes.ts @@ -15,6 +15,7 @@ export interface CTokenLendingTransactionParams { txType: CTokenLendingTxTypes; cToken: CTokenWithUserData; amount: string; + max: boolean; // for repay and withdraw, if all tokens should be repaid/withdrawn } export interface CLMClaimRewardsTxParams { diff --git a/hooks/lending/transactions/lending.ts b/hooks/lending/transactions/lending.ts index 4e09ac88..ad97035a 100644 --- a/hooks/lending/transactions/lending.ts +++ b/hooks/lending/transactions/lending.ts @@ -15,7 +15,8 @@ import { createApprovalTxs } from "@/utils/evm/erc20.utils"; import { TX_DESCRIPTIONS } from "@/config/consts/txDescriptions"; import { displayAmount } from "@/utils/tokenBalances.utils"; import { CERC20_ABI, COMPTROLLER_ABI } from "@/config/abis"; -import { getCantoCoreAddress } from "@/config/consts/addresses"; +import { MAX_UINT256, getCantoCoreAddress } from "@/config/consts/addresses"; +import { greaterThan } from "@/utils/tokens/tokenMath.utils"; export async function cTokenLendingTx( params: CTokenLendingTransactionParams @@ -92,34 +93,51 @@ export async function cTokenLendingTx( params.cToken.underlying.symbol, displayAmount(params.amount, params.cToken.underlying.decimals) ); - // check to see if tx is withdrawing entire balance - if ( - params.txType === CTokenLendingTxTypes.WITHDRAW && - params.cToken.userDetails.supplyBalanceInUnderlying === params.amount - ) { - // push special withdraw all function, passing in the cToken balance instead - txList.push( - _withdrawAllCTokenTx( - params.chainId, - params.cToken.address, - params.cToken.userDetails.balanceOfCToken, - txDescription - ) - ); - } else { - // push normal clm tx using underlying balance - txList.push( - _lendingCTokenTx( - params.txType, - params.chainId, - params.cToken.address, - isCanto, - params.amount, - txDescription - ) - ); + + // check if max was clicked + if (params.max) { + console.log("maxmax") + // check to see if tx is withdrawing entire balance + if ( + params.txType === CTokenLendingTxTypes.WITHDRAW && + params.cToken.userDetails.supplyBalanceInUnderlying === params.amount + ) { + // push special withdraw all function, passing in the cToken balance instead + txList.push( + _withdrawAllCTokenTx( + params.chainId, + params.cToken.address, + params.cToken.userDetails.balanceOfCToken, + txDescription + ) + ); + return NO_ERROR({ transactions: txList }); + } + // check to see if repaying entire balance + if ( + params.txType === CTokenLendingTxTypes.REPAY && + greaterThan( + params.cToken.userDetails.balanceOfUnderlying, + params.cToken.userDetails.borrowBalance + ).data + ) { + // change amount to max uint + params.amount = MAX_UINT256; + } } + // push normal clm tx using underlying balance + txList.push( + _lendingCTokenTx( + params.txType, + params.chainId, + params.cToken.address, + isCanto, + params.amount, + txDescription + ) + ); + // user should enable token as collateral if supplying and token has collateral factor if ( params.txType === CTokenLendingTxTypes.SUPPLY && diff --git a/hooks/pairs/cantoDex/transactions/pairsTx.ts b/hooks/pairs/cantoDex/transactions/pairsTx.ts index 1e32999c..fba6499e 100644 --- a/hooks/pairs/cantoDex/transactions/pairsTx.ts +++ b/hooks/pairs/cantoDex/transactions/pairsTx.ts @@ -227,6 +227,7 @@ async function removeLiquidityFlow( txType: CTokenLendingTxTypes.WITHDRAW, cToken: params.pair.clmData, amount: unstakeAmount, + max: true, }); if (withdrawError) { return NEW_ERROR("removeLiquidityFlow: " + errMsg(withdrawError)); @@ -360,6 +361,7 @@ export async function stakeLPFlow( : CTokenLendingTxTypes.WITHDRAW, cToken: params.cLPToken, amount: stakeAmount, + max: true, }); } diff --git a/hooks/pairs/newAmbient/config/ambientPools.ts b/hooks/pairs/newAmbient/config/ambientPools.ts index 1c06a3b0..769a12fd 100644 --- a/hooks/pairs/newAmbient/config/ambientPools.ts +++ b/hooks/pairs/newAmbient/config/ambientPools.ts @@ -25,7 +25,7 @@ const MAINNET_AMBIENT_POOLS: BaseAmbientPool[] = [ poolIdx: 36000, address: "0x80b5a32E4F032B2a058b4F29EC95EEfEEB87aDcd-0xEe602429Ef7eCe0a13e4FfE8dBC16e101049504C", - symbol: "cNoteUSDCLP", + symbol: "cNOTE / USDC", logoURI: "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/LP/cNoteUSDCLP.svg", stable: true, diff --git a/utils/evm/performEVMTx.ts b/utils/evm/performEVMTx.ts index 5803f6a2..63219a8c 100644 --- a/utils/evm/performEVMTx.ts +++ b/utils/evm/performEVMTx.ts @@ -11,6 +11,7 @@ import { BaseError } from "viem"; import { TransactionReceipt } from "web3"; import { asyncCallWithTimeout } from "../async.utils"; import { newContractInstance } from "./helpers.utils"; +import { percentOfAmount } from "../tokens/tokenMath.utils"; /** * @notice performs evm transaction @@ -46,6 +47,20 @@ export async function performEVMTransaction( signer: newSigner, }); if (contractError) throw contractError; + + // estimate gas so that metamask can show gas fee + const gasEstimate = await contractInstance.methods[tx.method]( + ...(tx.params as []) + ).estimateGas({ + from: newSigner.account.address, + value: tx.value, + }); + // make sure gas is at least base limit (21,000), then over estimate by 50% + const { data: gasLimit, error: gasError } = percentOfAmount( + gasEstimate < 21000 ? "21000" : gasEstimate.toString(), + 150 + ); + if (gasError) throw gasError; // if user doesn't sign in 30 seconds, throw timeout error const { data: transaction, error: timeoutError } = await asyncCallWithTimeout( @@ -53,6 +68,7 @@ export async function performEVMTransaction( await contractInstance.methods[tx.method](...(tx.params as [])).send({ from: newSigner.account.address, value: tx.value, + gas: gasLimit, }), 90000 ); diff --git a/utils/keplr/connectKeplr.ts b/utils/keplr/connectKeplr.ts index 2f67ea77..71dba5be 100644 --- a/utils/keplr/connectKeplr.ts +++ b/utils/keplr/connectKeplr.ts @@ -3,34 +3,40 @@ import { NO_ERROR, PromiseWithError, errMsg, - CosmosNetwork, } from "@/config/interfaces"; import { GasPrice, SigningStargateClient } from "@cosmjs/stargate"; +import { getNetworkInfoFromChainId, isCosmosNetwork } from "../networks.utils"; /** * @notice connects to keplr and returns client and address - * @param {CosmosNetwork} cosmosNetwork cosmos network to connect to + * @param {string} cosmosChainId cosmos chain id to connect to * @returns {PromiseWithError<{client: SigningStargateClient, address: string}>} client and address or error */ export async function connectToKeplr( - cosmosNetwork: CosmosNetwork + cosmosChainId: string ): PromiseWithError<{ client: SigningStargateClient; address: string }> { if (!window.keplr) { return NEW_ERROR("connectToKeplr: keplr not installed"); } try { - await window.keplr.enable(cosmosNetwork.chainId); - const offlineSigner = window.keplr.getOfflineSigner(cosmosNetwork.chainId); + // get network and make sure it's cosmos + const { data: network, error } = getNetworkInfoFromChainId(cosmosChainId); + if (error) throw error; + if (!isCosmosNetwork(network)) throw Error("invalid cosmos network"); + + // try to connect + await window.keplr.enable(network.chainId); + const offlineSigner = window.keplr.getOfflineSigner(network.chainId); const accounts = await offlineSigner.getAccounts(); if (!accounts.length) { return NEW_ERROR("connectToKeplr: no accounts found"); } const client = await SigningStargateClient.connectWithSigner( - cosmosNetwork.rpcUrl, + network.rpcUrl, offlineSigner, { gasPrice: GasPrice.fromString( - "300000" + cosmosNetwork.nativeCurrency.baseName + "300000" + network.nativeCurrency.baseName ), } );