mars-v2-frontend/pages/index.tsx
Gustavo Mauricio 1deba2059e
MP-1699: Trade on Margin Account (#52)
* update generated types

* added CRO to token info

* update contract addresses to match latest deployment

* feat: token prices fetched from oracle contract

* trade page initial commit

* trade asset action hook

* extract max swap amount logic into custom hook

* trade component ui adjustments

* trade container min-width and some styling improvements

* trade success message and loading indicator

* normalize naming conventions on trading

* max swap amount formula adjustments

* trade execute msg with borrow. code cleanup

* fix: click max update tokenOut amount. remove wallet from fund denom

* delay token amount decimal conversion. input formatting

* increase hardcoded gas

* renamed max swappable amount hook

* display token prices and market information on landing page

* reset trade amounts when selected account change

* max trade amount cleanup and minor performance optimizations

* fix: liabilities value with 1 hour interest buffer for trade action

* add token symbol to wallet and account labels

* swap trade pairs icon and basic functionality

* remove unnecessary optional chaining. comment adjusted

* refactor useTokenPrices to build query dynamically on tokens data

* extracted trade container and respective functionality into separate file

* fix: properly calculate positions after full swap

* mp-1218: trading using wallet
2022-11-22 10:14:12 +01:00

357 lines
10 KiB
TypeScript

// import Head from "next/head";
// import Image from "next/image";
// import styles from "../styles/Home.module.css";
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { useQueryClient } from '@tanstack/react-query'
import BigNumber from 'bignumber.js'
import type { NextPage } from 'next'
import React, { useEffect, useState } from 'react'
import { toast } from 'react-toastify'
import Button from 'components/Button'
import Container from 'components/Container'
import Spinner from 'components/Spinner'
import { contractAddresses } from 'config/contracts'
// import { Coin } from "@cosmjs/stargate";
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { queryKeys } from 'types/query-keys-factory'
import { chain } from 'utils/chains'
import { hardcodedFee } from 'utils/contants'
import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
const Home: NextPage = () => {
const [sendAmount, setSendAmount] = useState('')
const [recipientAddress, setRecipientAddress] = useState('')
const [allTokens, setAllTokens] = useState<string[] | null>(null)
const [walletTokens, setWalletTokens] = useState<string[] | null>(null)
const [borrowAmount, setBorrowAmount] = useState(0)
const [error, setError] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const queryClient = useQueryClient()
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
useEffect(() => {
;(async () => {
if (!window.keplr) return
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
setSigningClient(clientInstance)
})()
}, [address])
const handleSendClick = async () => {
setError(null)
setIsLoading(true)
try {
// console.log(await signingClient.getHeight());
// console.log(
// "contract info",
// signingClient.getContract(
// "osmo1zf26ahe5gqjtvnedh7ems7naf2wtw3z4ll6atf3t0hptal8ss4vq2mlx6w"
// )
// );
const res = await signingClient?.sendTokens(
address,
recipientAddress,
[
{
denom: chain.stakeCurrency.coinMinimalDenom,
amount: BigNumber(sendAmount)
.times(10 ** chain.stakeCurrency.coinDecimals)
.toString(),
},
],
hardcodedFee,
)
console.log('txResponse', res)
toast.success(
<div>
<a
href={`https://testnet.mintscan.io/osmosis-testnet/txs/${res?.transactionHash}`}
target='_blank'
rel='noreferrer'
>
Check transaction
</a>
</div>,
{ autoClose: false },
)
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
const handleCreateCreditAccount = async () => {
setError(null)
setIsLoading(true)
try {
// 200000 gas used
const executeMsg = {
create_credit_account: {},
}
const createResult = await signingClient?.execute(
address,
contractAddresses.creditManager,
executeMsg,
hardcodedFee,
)
console.log('mint result', createResult)
toast.success(
<div>
<a
href={`https://testnet.mintscan.io/osmosis-testnet/txs/${createResult?.transactionHash}`}
target='_blank'
rel='noreferrer'
>
Check transaction
</a>
</div>,
{ autoClose: false },
)
queryClient.invalidateQueries(queryKeys.creditAccounts(address))
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
// https://github.com/mars-protocol/rover/blob/master/scripts/types/generated/account-nft/AccountNft.types.ts
const handleGetCreditAccounts = async () => {
setError(null)
setIsLoading(true)
try {
const allTokensQueryMsg = {
all_tokens: {},
}
const allTokensResponse = await signingClient?.queryContractSmart(
contractAddresses.accountNft,
allTokensQueryMsg,
)
setAllTokens(allTokensResponse.tokens)
console.log('all tokens', allTokensResponse)
// Returns de owner of a specific "credit account"
// const ownerOfQueryMsg = {
// owner_of: {
// include_expired: false,
// token_id: "1",
// },
// };
// const ownerResponse = await signingClient.queryContractSmart(
// contractAddresses.accountNft,
// ownerOfQueryMsg
// );
// console.log("res owner", ownerResponse);
const tokensQueryMsg = {
tokens: {
owner: address,
},
}
const tokensResponse = await signingClient?.queryContractSmart(
contractAddresses.accountNft,
tokensQueryMsg,
)
console.log('res tokens', tokensResponse)
setWalletTokens(tokensResponse.tokens)
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
const handleBorrowClick = async () => {
setError(null)
setIsLoading(true)
try {
if (!selectedAccount) return
const executeMsg = {
update_credit_account: {
account_id: selectedAccount,
actions: [
{
borrow: {
denom: 'uosmo',
amount: BigNumber(borrowAmount)
.times(10 ** 6)
.toString(),
},
},
],
},
}
const borrowResult = await signingClient?.execute(
address,
contractAddresses.creditManager,
executeMsg,
hardcodedFee,
)
console.log('borrow result', borrowResult)
toast.success(
<div>
<a
href={`https://testnet.mintscan.io/osmosis-testnet/txs/${borrowResult?.transactionHash}`}
target='_blank'
rel='noreferrer'
>
Check transaction
</a>
</div>,
{ autoClose: false },
)
queryClient.invalidateQueries(queryKeys.creditAccounts(address))
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount))
queryClient.invalidateQueries(queryKeys.tokenBalance(address, 'uosmo'))
} catch (e: any) {
console.log(e)
setError(e.message)
} finally {
setIsLoading(false)
}
}
return (
<div className='mx-auto flex max-w-6xl flex-col gap-y-6'>
<Container>
<h4 className='mb-5 text-xl'>Send Tokens</h4>
<div className='mb-5 flex flex-wrap gap-2'>
<div>
<p>Address:</p>
<input
className='rounded-lg bg-black/40 px-3 py-1'
value={recipientAddress}
placeholder='address'
onChange={(e) => setRecipientAddress(e.target.value)}
/>
</div>
<div>
<p>Amount:</p>
<input
type='number'
className='rounded-lg bg-black/40 px-3 py-1'
value={sendAmount}
placeholder='amount'
onChange={(e) => setSendAmount(e.target.value)}
/>
</div>
</div>
<Button className='bg-[#524bb1] hover:bg-[#6962cc]' onClick={handleSendClick}>
Send
</Button>
</Container>
<Container>
<h4 className='mb-5 text-xl'>Create Credit Account (Mint NFT)</h4>
<Button className='bg-[#524bb1] hover:bg-[#6962cc]' onClick={handleCreateCreditAccount}>
Create
</Button>
</Container>
<Container>
<h4 className='mb-5 text-xl'>Get all Credit Accounts</h4>
<Button className='bg-[#524bb1] hover:bg-[#6962cc]' onClick={handleGetCreditAccounts}>
Fetch
</Button>
</Container>
<Container>
<h4 className='mb-5 text-xl'>Borrow OSMO</h4>
<input
className='rounded-lg bg-black/40 px-3 py-1'
type='number'
onChange={(e) => setBorrowAmount(e.target.valueAsNumber)}
/>
<Button className='ml-4 bg-[#524bb1] hover:bg-[#6962cc]' onClick={handleBorrowClick}>
Borrow
</Button>
</Container>
<div>
{allTokens && (
<div className='mb-4'>
<div className='flex items-end'>
<h5 className='text-xl font-medium'>All Tokens</h5>
<p className='ml-2 text-sm'>- {allTokens.length} total</p>
</div>
{allTokens.map((token) => (
<p key={token}>{token}</p>
))}
</div>
)}
{walletTokens && (
<>
<div className='flex items-end'>
<h5 className='text-xl font-medium'>Your Tokens</h5>
<p className='ml-2 text-sm'>- {walletTokens.length} total</p>
</div>
{walletTokens.map((token) => (
<p key={token}>{token}</p>
))}
</>
)}
</div>
<div>
{tokenPrices && (
<div className='mb-6'>
<h3 className='text-xl font-semibold'>Token Prices:</h3>
<pre>{JSON.stringify(tokenPrices, null, 2)}</pre>
</div>
)}
{marketsData && (
<div>
<h3 className='text-xl font-semibold'>Markets Data:</h3>
<pre>{JSON.stringify(marketsData, null, 2)}</pre>
</div>
)}
</div>
{error && <div className='mt-8 bg-white p-4 text-red-500'>{error}</div>}
{isLoading && (
<div>
<Spinner />
</div>
)}
</div>
)
}
export default Home