diff --git a/src/components/DepositDialog.jsx b/src/components/DepositDialog.jsx index 975f6835f..2c214901a 100644 --- a/src/components/DepositDialog.jsx +++ b/src/components/DepositDialog.jsx @@ -1,19 +1,52 @@ -import React from 'react'; -import { Modal } from 'antd'; +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { Modal, Button } from 'antd'; import { useSelectedBaseCurrencyAccount, useMarket, useSelectedQuoteCurrencyAccount, } from '../utils/markets'; import { useWallet } from '../utils/wallet'; -import Link from './Link'; +import { useConnection } from '../utils/connection'; +import { createTokenAccount } from '../utils/send'; +import QRCode from 'qrcode.react'; +import WalletConnect from './WalletConnect'; +import { notify } from '../utils/notifications'; + +const ActionButton = styled(Button)` + color: #2abdd2; + background-color: #212734; + border-width: 0px; +`; export default function DepositDialog({ onClose, baseOrQuote }) { const { market, baseCurrency, quoteCurrency } = useMarket(); - const { providerName, providerUrl } = useWallet(); + const { connected, wallet } = useWallet(); + const connection = useConnection(); const baseCurrencyAccount = useSelectedBaseCurrencyAccount(); const quoteCurrencyAccount = useSelectedQuoteCurrencyAccount(); + + const [isCreatingTokenAccount, setIsCreatingTokenAccount] = useState(false); + + const doCreateTokenAccount = async () => { + try { + setIsCreatingTokenAccount(true); + await createTokenAccount({ + wallet, + connection, + mintPublicKey: coinMint, + }); + } catch (e) { + notify({ + message: 'Error creating token account: ' + e.message, + type: 'error', + }); + } finally { + setIsCreatingTokenAccount(false); + } + }; + let coinMint; let account; let depositCoin; @@ -31,6 +64,7 @@ export default function DepositDialog({ onClose, baseOrQuote }) { if (!coinMint) { return null; } + return ( -
-

Mint address:

-

{coinMint.toBase58()}

-
-

SPL Deposit address:

-

- {account ? ( - account.pubkey.toBase58() - ) : ( - <> - Visit{' '} - - {providerName} - {' '} - to create an account for this mint - - )} -

-
+
+

SPL Deposit address:

+

+ {account ? ( + account.pubkey.toBase58() + ) : ( + <> + {connected ? ( + + Create token account + + ) : ( + + )} + + )} +

+ {account && }
); diff --git a/src/components/StandaloneBalancesDisplay.jsx b/src/components/StandaloneBalancesDisplay.jsx index 4dc7ab9d1..e3b84d650 100644 --- a/src/components/StandaloneBalancesDisplay.jsx +++ b/src/components/StandaloneBalancesDisplay.jsx @@ -10,10 +10,12 @@ import { useSelectedQuoteCurrencyAccount, } from '../utils/markets'; import DepositDialog from './DepositDialog'; +import TransferDialog from './TransferDialog'; import { useWallet } from '../utils/wallet'; import Link from './Link'; import { settleFunds } from '../utils/send'; import { useSendConnection } from '../utils/connection'; +import WalletConnect from './WalletConnect'; import { notify } from '../utils/notifications'; const RowBox = styled(Row)` @@ -25,6 +27,10 @@ const Tip = styled.p` padding-top: 6px; `; +const Label = styled.span` + color: rgba(255, 255, 255, 0.5); +`; + const ActionButton = styled(Button)` color: #2abdd2; background-color: #212734; @@ -36,8 +42,10 @@ export default function StandaloneBalancesDisplay() { const balances = useBalances(); const openOrdersAccount = useSelectedOpenOrdersAccount(true); const connection = useSendConnection(); - const { providerUrl, providerName, wallet } = useWallet(); + const { providerUrl, providerName, wallet, connected } = useWallet(); const [baseOrQuote, setBaseOrQuote] = useState(''); + const [transferCoin, setTransferCoin] = useState(null); + const baseCurrencyAccount = useSelectedBaseCurrencyAccount(); const quoteCurrencyAccount = useSelectedQuoteCurrencyAccount(); const baseCurrencyBalances = @@ -67,39 +75,91 @@ export default function StandaloneBalancesDisplay() { return ( {[ - [baseCurrency, baseCurrencyBalances, 'base'], - [quoteCurrency, quoteCurrencyBalances, 'quote'], - ].map(([currency, balances, baseOrQuote], index) => ( + [ + market?.baseMintAddress, + baseCurrency, + baseCurrencyAccount, + baseCurrencyBalances, + 'base', + ], + [ + market?.quoteMintAddress, + quoteCurrency, + quoteCurrencyAccount, + quoteCurrencyBalances, + 'quote', + ], + ].map(([mint, currency, account, balances, baseOrQuote], index) => ( {currency} - - Wallet balance: - {balances && balances.wallet} - - - Unsettled balance: - {balances && balances.unsettled} - + {connected ? ( + <> + + + + + {balances && balances.wallet} + + + + + + {balances && balances.unsettled} + + + ) : ( +
+ +
+ )} - + setBaseOrQuote(baseOrQuote)} > Deposit - - + + + setTransferCoin({ + coin: currency, + source: account?.pubkey, + mint, + }) + } + > + Transfer + + + + Settle @@ -117,6 +177,10 @@ export default function StandaloneBalancesDisplay() { baseOrQuote={baseOrQuote} onClose={() => setBaseOrQuote('')} /> + setTransferCoin(null)} + />
); } diff --git a/src/components/TopBar.js b/src/components/TopBar.js index 839eb0776..378ac6eeb 100644 --- a/src/components/TopBar.js +++ b/src/components/TopBar.js @@ -7,6 +7,7 @@ import styled from 'styled-components'; import { useWallet, WALLET_PROVIDERS } from '../utils/wallet'; import { ENDPOINTS, useConnectionConfig } from '../utils/connection'; import LinkAddress from './LinkAddress'; +import WalletConnect from './WalletConnect'; const Wrapper = styled.div` background-color: #0d1017; @@ -86,15 +87,7 @@ export default function TopBar() {
- + {connected && ( } diff --git a/src/components/TransferDialog.jsx b/src/components/TransferDialog.jsx new file mode 100644 index 000000000..b46b640bd --- /dev/null +++ b/src/components/TransferDialog.jsx @@ -0,0 +1,118 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { Modal, Input, Button, Row, Col, Typography } from 'antd'; +import { useBalances } from '../utils/markets'; +import { useWallet } from '../utils/wallet'; +import { useConnection } from '../utils/connection'; +import { isValidPublicKey } from '../utils/utils'; +import { transferToken } from '../utils/send'; +import { notify } from '../utils/notifications'; +import { PublicKey } from '@solana/web3.js'; +import { getMintDecimals } from '../utils/tokens'; + +const { Text } = Typography; + +const InputField = styled(Input)` + margin-bottom: 8px; +`; + +const ActionButton = styled(Button)` + color: #2abdd2; + background-color: #212734; + border-width: 0px; +`; + +export default function TransferDialog({ onClose, transferCoin }) { + const { connected, wallet } = useWallet(); + const connection = useConnection(); + const balances = useBalances(); + + const [destination, setDestination] = useState(null); + const [amount, setAmount] = useState(null); + const [transferInProgress, setTransferInProgress] = useState(false); + + const balance = + balances && balances.find((b) => b.coin === transferCoin?.coin)?.wallet; + + const onDoClose = () => { + setDestination(null); + setAmount(null); + onClose(); + }; + + const onTransfer = async () => { + try { + const decimals = await getMintDecimals(connection, transferCoin?.mint); + if (!decimals) { + notify({ message: 'Could not get source data', type: 'error' }); + return; + } + + const parsedAmount = Math.round(amount * 10 ** decimals); + setTransferInProgress(true); + await transferToken({ + wallet, + connection, + source: transferCoin?.source, + destination: new PublicKey(destination), + amount: parsedAmount, + }); + } catch (e) { + notify({ + message: 'Error transferring tokens: ' + e.message, + type: 'error', + }); + } finally { + setTransferInProgress(false); + onDoClose(); + } + }; + + const canSubmit = + connected && + isValidPublicKey(destination) && + amount > 0 && + amount <= balance; + + return ( + +
+ {destination && !isValidPublicKey(destination) && ( + + + Invalid address + + + )} + setDestination(e.target.value)} + /> + {amount > balance && ( + + + Not enough balances + + + )} + setAmount(e.target.value)} + type="number" + /> + setAmount(balance)}> + Max: {balance} + +
+
+ ); +} diff --git a/src/components/WalletConnect.jsx b/src/components/WalletConnect.jsx new file mode 100644 index 000000000..6faf0f19a --- /dev/null +++ b/src/components/WalletConnect.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Button } from 'antd'; +import { UserOutlined } from '@ant-design/icons'; +import { useWallet } from '../utils/wallet'; + +export default function WalletConnect() { + const { connected, wallet } = useWallet(); + + return ( + + ); +} diff --git a/src/pages/ListNewMarketPage.jsx b/src/pages/ListNewMarketPage.jsx index 68618474c..e61dd2064 100644 --- a/src/pages/ListNewMarketPage.jsx +++ b/src/pages/ListNewMarketPage.jsx @@ -30,7 +30,13 @@ export default function ListNewMarketPage() { Base Token Mint Address{' '} - (e.g. BTC solana address: {9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E}) + (e.g. BTC solana address:{' '} + { + + 9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E + + } + ) , 'The base token is the token being traded. For example, the base token of a BTC/USDT market is BTC.', @@ -40,7 +46,13 @@ export default function ListNewMarketPage() { Quote Token Mint Address{' '} - (e.g. USDT solana address: {BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4}) + (e.g. USDT solana address:{' '} + { + + BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4 + + } + ) , 'The quote token is the token used to price trades. For example, the quote token of a BTC/USDT market is USDT.', @@ -106,7 +118,7 @@ export default function ListNewMarketPage() {
{baseMintInput} @@ -114,7 +126,8 @@ export default function ListNewMarketPage() { - Minimum Order Size (Lot size in e.g. BTC) + Minimum Order Size{' '} + (Lot size in e.g. BTC) } name="lotSize" @@ -139,7 +152,8 @@ export default function ListNewMarketPage() { - Tick Size (Price increment in e.g. USDT) + Tick Size{' '} + (Price increment in e.g. USDT) } name="tickSize" diff --git a/src/utils/send.js b/src/utils/send.js index e08c2a69f..156eeaa69 100644 --- a/src/utils/send.js +++ b/src/utils/send.js @@ -14,6 +14,38 @@ import { TokenInstructions, } from '@project-serum/serum'; +export async function transferToken({ + wallet, + connection, + source, + destination, + amount, +}) { + let transaction = source.equals(wallet.publicKey) + ? SystemProgram.transfer({ + fromPubkey: wallet.publicKey, + toPubkey: destination, + lamports: amount, + }) + : new Transaction().add( + TokenInstructions.transfer({ + source, + destination, + owner: wallet.publicKey, + amount, + }), + ); + + return await sendTransaction({ + transaction, + wallet, + connection, + sendingMessage: 'Transferring tokens...', + sentMessage: 'Tokens transferred', + successMessage: 'Token transfer confirmed', + }); +} + export async function createTokenAccountTransaction({ connection, wallet, @@ -41,6 +73,28 @@ export async function createTokenAccountTransaction({ }; } +export async function createTokenAccount({ + connection, + wallet, + mintPublicKey, +}) { + const { transaction, signer } = await createTokenAccountTransaction({ + connection, + wallet, + mintPublicKey, + }); + + return await sendTransaction({ + transaction, + signers: [signer, wallet.publicKey], + wallet, + connection, + sendingMessage: 'Creating token account...', + sentMessage: 'Token account created', + successMessage: 'Token account creation confirmed', + }); +} + export async function settleFunds({ market, openOrders, diff --git a/src/utils/tokens.js b/src/utils/tokens.js index 4f6fbc003..febb0842f 100644 --- a/src/utils/tokens.js +++ b/src/utils/tokens.js @@ -26,6 +26,15 @@ export function parseTokenAccountData(data) { }; } +export async function getMintDecimals(connection, publicKey) { + const mintInfo = await connection.getAccountInfo(publicKey); + if (!mintInfo?.data) { + return; + } + let { decimals } = parseTokenMintData(mintInfo.data); + return decimals; +} + export function parseTokenMintData(data) { let { decimals, initialized } = MINT_LAYOUT.decode(data); return { decimals, initialized };