Skip to content
This repository was archived by the owner on Oct 1, 2024. It is now read-only.

feat: combine mint token function, remove - as it was confusing #13

Merged
merged 8 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions components/Markets/ConditionalMarketCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function ConditionalMarketCard({ isPassMarket = false }: { isPassMarket?:
const { generateExplorerLink } = useExplorerConfiguration();
const { colorScheme } = useMantineColorScheme();
const [isPlacingOrder, setIsPlacingOrder] = useState(false);

const { amount: baseBalance, fetchAmount: fetchBase } = useBalance(
isPassMarket
? markets?.baseVault.conditionalOnFinalizeTokenMint
Expand Down Expand Up @@ -202,6 +203,7 @@ export function ConditionalMarketCard({ isPassMarket = false }: { isPassMarket?:
}
return 'inherit';
};

const handlePlaceOrder = useCallback(async () => {
try {
setIsPlacingOrder(true);
Expand Down
100 changes: 65 additions & 35 deletions components/Proposals/MarketsBalances.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,73 @@
import { Group, HoverCard, Stack, Text } from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
import { useTokens } from '@/hooks/useTokens';
import { Fieldset, Group, Stack, Indicator, Text } from '@mantine/core';
import { IconExternalLink } from '@tabler/icons-react';
import Image from 'next/image';
import Link from 'next/link';
import { useMemo } from 'react';
import { MintConditionalTokenCard } from './MintConditionalTokenCard';
import { useExplorerConfiguration } from '@/hooks/useExplorerConfiguration';
import useConditionalTokens, { ConditionalToken } from '@/hooks/useConditionalTokens';

export default function MarketsBalances() {
const { tokens } = useTokens();
function Balance({ token, market, imageSrc }: { token: ConditionalToken, market: 'pass' | 'fail', imageSrc: string }) {
const { explorer, generateExplorerLink } = useExplorerConfiguration();

const { balance, address } = useMemo(() => {
if (market === 'pass') {
return (
{
balance: token.balancePass?.uiAmountString || 0,
address: token.finalize.toString(),
});
}
return { balance: token.balanceFail?.uiAmountString || 0, address: token.revert.toString() };
}, [market, token]);

return (
<Group align="center" justify="center" pos="relative" pt="lg" wrap="wrap" grow>
<HoverCard position="top">
<HoverCard.Target>
<Group pos="absolute" top="0" left="0" justify="center" align="flex-start">
<IconInfoCircle strokeWidth={1.3} />
<Group gap={12}>
<Indicator label={market.charAt(0).toUpperCase()} size={16} offset={10} position="bottom-end" color={market === 'pass' ? '#F1F3F5' : '#1F1F1F'} withBorder autoContrast>
<Image src={imageSrc} alt={`${token.symbol} logo`} width={40} height={40} style={{ marginTop: 4 }} />
</Indicator>
<Stack gap={0}>
<Group gap={4}>
<Text fw={600}>
{balance} {market.charAt(0)}{token.symbol}
</Text>
</Group>
<Link
target="_blank"
href={generateExplorerLink(address, 'account')}
>
<Group gap="4" justify="center" ta="center" opacity={0.5}>
<Text size="xs" fw="lighter">see on {explorer}</Text>
<IconExternalLink height="1rem" width="1rem" stroke="1px" />
</Group>
</HoverCard.Target>
<HoverCard.Dropdown w="22rem">
<Stack>
<Text>
Conditional tokens are the tokens used to trade on conditional markets. You can mint
some by depositing $META or $USDC. These tokens will be locked up until the proposal
is finalized.
</Text>
<Text size="sm">
<Text span fw="bold">
Pass tokens (pTokens){' '}
</Text>
are used to trade on the Pass Market
</Text>
<Text size="sm">
<Text span fw="bold">
Fail tokens (fTokens){' '}
</Text>
are used to trade on the Fail Market.
</Text>
</Stack>
</HoverCard.Dropdown>
</HoverCard>
{tokens?.meta ? <MintConditionalTokenCard token={tokens.meta} /> : null}
{tokens?.usdc ? <MintConditionalTokenCard token={tokens.usdc} /> : null}
</Link>
</Stack>
</Group>
);
}

export default function MarketsBalances() {
const { metaToken, usdcToken } = useConditionalTokens();

return (
<Stack align="center" justify="center" pos="relative" pt="lg" w="100%">
<MintConditionalTokenCard />
{metaToken && usdcToken &&
<Group gap={24} w="100%">
<Fieldset legend="Pass market" flex={1}>
<Stack>
<Balance token={metaToken} market="pass" imageSrc="/metaToken.png" />
<Balance token={usdcToken} market="pass" imageSrc="https://s3.coinmarketcap.com/static-gravity/image/5a8229787b5e4c809b5914eef709b59a.png" />
</Stack>
</Fieldset>
<Fieldset legend="Fail market" flex={1}>
<Stack>
<Balance token={metaToken} market="fail" imageSrc="/metaToken.png" />
<Balance token={usdcToken} market="fail" imageSrc="https://s3.coinmarketcap.com/static-gravity/image/5a8229787b5e4c809b5914eef709b59a.png" />
</Stack>
</Fieldset>
</Group>
}
</Stack>
);
}
190 changes: 113 additions & 77 deletions components/Proposals/MintConditionalTokenCard.tsx
Original file line number Diff line number Diff line change
@@ -1,101 +1,137 @@
import { useCallback, useState } from 'react';
import { Button, Fieldset, Group, Text, TextInput } from '@mantine/core';
import { useCallback, useEffect, useState } from 'react';
import { Button, Fieldset, Group, Text, TextInput, SegmentedControl, Loader, Stack, HoverCard } from '@mantine/core';
import numeral from 'numeral';
import Link from 'next/link';
import { IconExternalLink } from '@tabler/icons-react';
import { Token } from '@/hooks/useTokens';
import { BN } from '@coral-xyz/anchor';
import { IconInfoCircle } from '@tabler/icons-react';
import { PublicKey } from '@solana/web3.js';
import { useProposal } from '@/contexts/ProposalContext';
import { useTransactionSender } from '../../hooks/useTransactionSender';
import { useExplorerConfiguration } from '@/hooks/useExplorerConfiguration';
import { useBalance } from '../../hooks/useBalance';
import { NUMERAL_FORMAT } from '../../lib/constants';
import { Token } from '@/hooks/useTokens';
import useConditionalTokens from '@/hooks/useConditionalTokens';

export function MintConditionalTokenCard({ token }: { token: Token }) {
interface Balance {
token: Token;
symbol: string;
balanceSpot: BN;
balancePass: BN;
balanceFail: BN;
fetchUnderlying: () => Promise<void>
fetchPass: () => Promise<void>,
fetchFail: () => Promise<void>
finalize: PublicKey;
revert: PublicKey;
}

export function MintConditionalTokenCard() {
const sender = useTransactionSender();
const { markets, mintTokensTransactions } = useProposal();
const { generateExplorerLink } = useExplorerConfiguration();

if (!markets) return null;

const [mintAmount, setMintAmount] = useState<number>();
const fromBase = markets.baseVault.underlyingTokenMint.equals(token.publicKey);
const vault = fromBase ? markets.baseVault : markets.quoteVault;
const { amount, fetchAmount: fetchUnderlying } = useBalance(vault.underlyingTokenMint);
const { amount: passAmount, fetchAmount: fetchPass } = useBalance(
vault.conditionalOnFinalizeTokenMint,
);
const { amount: failAmount, fetchAmount: fetchFail } = useBalance(
vault.conditionalOnRevertTokenMint,
);
const [isMinting, setIsMinting] = useState(false);
if (!markets) return null;

const { metaToken, usdcToken } = useConditionalTokens();

const [token, setToken] = useState<Balance | undefined>();

useEffect(() => {
setToken((prev) => {
if (!prev) return metaToken;
return prev.symbol === 'META' ? metaToken : usdcToken;
}
);
}, [metaToken, usdcToken]);

const updateSelectedToken = (e: string) => {
if (e === 'META') setToken(metaToken);
else if (e === 'USDC') {
setToken(usdcToken);
}
};

const handleMint = useCallback(async () => {
if (!mintAmount || !markets) return;
if (!mintAmount) return;

setIsMinting(true);
const fromBase = token?.symbol !== 'USDC';
try {
const txs = await mintTokensTransactions(mintAmount!, fromBase);
const txs = await mintTokensTransactions(mintAmount, fromBase);

if (!txs) return;

await sender.send(txs);
fetchUnderlying();
fetchPass();
fetchFail();
token?.fetchUnderlying();
token?.fetchFail();
token?.fetchPass();
} finally {
setIsMinting(false);
}
}, [mintTokensTransactions, fetchUnderlying, fetchPass, fetchFail, amount, sender]);
}, [mintTokensTransactions, sender, mintAmount, token]);

return (
<Fieldset legend={`Mint conditional $${token.symbol}`} miw="180px">
<TextInput
label="Amount"
description={`Balance: ${numeral(amount?.uiAmountString || 0).format(NUMERAL_FORMAT)} $${
token.symbol
}`}
placeholder="Amount to mint"
type="number"
onChange={(e) => setMintAmount(Number(e.target.value))}
/>
<Text fw="lighter" size="sm">
Balances:
</Text>
<Text fw="lighter" size="sm" c="green">
- {passAmount?.uiAmountString || 0} $p{token.symbol}
</Text>
<Text fw="lighter" size="sm" c="red">
- {failAmount?.uiAmountString || 0} $f{token.symbol}
</Text>
<Button
mt="md"
disabled={(mintAmount || 0) <= 0}
loading={isMinting}
onClick={handleMint}
fullWidth
>
Mint
</Button>
<Group mt="md" justify="space-between">
<Link
target="_blank"
href={generateExplorerLink(vault.conditionalOnFinalizeTokenMint.toString(), 'account')}
>
<Group gap="0" justify="center" ta="center" c="green">
<Text size="xs">p{token.symbol}</Text>
<IconExternalLink height="1rem" width="1rem" />
</Group>
</Link>
<Link
target="_blank"
href={generateExplorerLink(vault.conditionalOnRevertTokenMint.toString(), 'account')}
>
<Group gap="0" align="center" c="red">
<Text size="xs">f{token.symbol}</Text>
<IconExternalLink height="1rem" width="1rem" />
</Group>
</Link>
return !token ?
(
<Group justify="center">
<Loader />
</Group>
</Fieldset>
);
) : (
<Fieldset legend="Mint Conditional Tokens" miw="350px" w="100%" pos="relative">
<HoverCard position="top">
<HoverCard.Target>
<Group pos="absolute" top="-10px" right="0" justify="center" align="flex-start">
<IconInfoCircle strokeWidth={1.3} />
</Group>
</HoverCard.Target>
<HoverCard.Dropdown w="22rem">
<Stack>
<Text>
Conditional tokens are the tokens used to trade on conditional markets. You can mint
some by depositing $META or $USDC. These tokens will be locked up until the proposal
is finalized.
</Text>
<Text size="sm">
<Text span fw="bold">
Pass tokens (pTokens){' '}
</Text>
are used to trade on the Pass Market
</Text>
<Text size="sm">
<Text span fw="bold">
Fail tokens (fTokens){' '}
</Text>
are used to trade on the Fail Market.
</Text>
</Stack>
</HoverCard.Dropdown>
</HoverCard>
<SegmentedControl
style={{ marginTop: '10px' }}
color="#4e4e4e"
value={token.symbol}
className="label"
onChange={(e) =>
updateSelectedToken(e)
}
fullWidth
data={['META', 'USDC']}
/>
<TextInput
label="Amount"
description={`Balance: ${numeral(token.balanceSpot?.uiAmountString || 0).format(NUMERAL_FORMAT)} $${token.token.symbol
}`}
placeholder="Amount to mint"
type="number"
onChange={(e) => setMintAmount(Number(e.target.value))}
/>

<Button
mt="md"
disabled={(mintAmount || 0) <= 0}
loading={isMinting}
onClick={handleMint}
fullWidth
>
Mint {mintAmount ? `${mintAmount} p${token.symbol} and ${mintAmount} f${token.symbol}` : ''}
</Button>
</Fieldset>
);
}
13 changes: 13 additions & 0 deletions contexts/BalancesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ type Balances = { [token: string]: TokenAmount };
export interface BalancesInterface {
balances: Balances;
fetchBalance: (mint: PublicKey) => Promise<TokenAmount>;
getBalance: (mint: PublicKey) => Promise<TokenAmount>;
}

export const balancesContext = createContext<BalancesInterface>({
balances: {},
fetchBalance: () => new Promise(() => {}),
getBalance: () => new Promise(() => { }),
});

export const useBalances = () => {
Expand Down Expand Up @@ -63,11 +65,22 @@ export function BalancesProvider({
[connection, owner],
);

const getBalance = useCallback(
async (mint: PublicKey | string) => {
if (Object.prototype.hasOwnProperty.call(balances, mint.toString())) {
return balances[mint.toString()];
}
return fetchBalance(mint);
},
[balances, fetchBalance],
);

return (
<balancesContext.Provider
value={{
balances,
fetchBalance,
getBalance,
}}
>
{children}
Expand Down
6 changes: 3 additions & 3 deletions hooks/useBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect } from 'react';
import { defaultAmount, useBalances } from '../contexts/BalancesContext';

export function useBalance(mint?: PublicKey) {
const { balances, fetchBalance } = useBalances();
const { balances, getBalance, fetchBalance } = useBalances();

const fetchAmount = async () => {
if (mint) {
Expand All @@ -13,9 +13,9 @@ export function useBalance(mint?: PublicKey) {

useEffect(() => {
if (mint) {
fetchBalance(mint);
getBalance(mint);
}
}, [mint, fetchBalance]);
}, [mint, getBalance]);

return { amount: mint ? balances[mint.toString()] : defaultAmount, fetchAmount };
}
Loading
Loading