From ea6a32983e49471dd0381fe8f3effc03326bd0f6 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Tue, 1 Sep 2020 10:04:13 -0700 Subject: [PATCH] Add SOL markets --- package.json | 2 +- src/components/TradeForm.jsx | 17 ++++--- src/routes.js | 5 -- src/utils/connection.js | 2 +- src/utils/markets.js | 89 +++++++++++++++++------------------- src/utils/send.js | 69 +++++++--------------------- yarn.lock | 8 ++-- 7 files changed, 73 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index c0a992c..3f77d2e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "@ant-design/icons": "^4.2.1", "@craco/craco": "^5.6.4", - "@project-serum/serum": "^0.9.2", + "@project-serum/serum": "^0.11.1", "@project-serum/sol-wallet-adapter": "^0.1.0", "@solana/web3.js": "^0.71.9", "@testing-library/jest-dom": "^4.2.4", diff --git a/src/components/TradeForm.jsx b/src/components/TradeForm.jsx index 315ef25..d0f5ee4 100644 --- a/src/components/TradeForm.jsx +++ b/src/components/TradeForm.jsx @@ -45,8 +45,8 @@ const sliderMarks = { export default function TradeForm({ style, setChangeOrderRef }) { const [side, setSide] = useState('buy'); const { baseCurrency, quoteCurrency, market } = useMarket(); - const [baseCurrencyBalances] = useBaseCurrencyBalances(); - const [quoteCurrencyBalances] = useQuoteCurrencyBalances(); + const baseCurrencyBalances = useBaseCurrencyBalances(); + const quoteCurrencyBalances = useQuoteCurrencyBalances(); const baseCurrencyAccount = useSelectedBaseCurrencyAccount(); const quoteCurrencyAccount = useSelectedQuoteCurrencyAccount(); const openOrdersAccount = useSelectedOpenOrdersAccount(true); @@ -130,7 +130,7 @@ export default function TradeForm({ style, setChangeOrderRef }) { setSubmitting(true); try { - !(await placeOrder({ + await placeOrder({ side, price: parsedPrice, size: parsedSize, @@ -138,14 +138,13 @@ export default function TradeForm({ style, setChangeOrderRef }) { market, connection: sendConnection, wallet, - baseCurrencyAccount: baseCurrencyAccount?.pubkey?.toBase58(), - quoteCurrencyAccount: quoteCurrencyAccount?.pubkey?.toBase58(), - openOrdersAccount: openOrdersAccount?.pubkey?.toBase58(), - onBeforeSendCallBack: () => setSubmitting(true), - onConfirmCallBack: () => setSubmitting(false), - })) && setSubmitting(false); + baseCurrencyAccount: baseCurrencyAccount?.pubkey, + quoteCurrencyAccount: quoteCurrencyAccount?.pubkey, + }); } catch (e) { + console.warn(e); notify({ message: 'Error placing order: ' + e.message, type: 'error' }); + } finally { setSubmitting(false); } } diff --git a/src/routes.js b/src/routes.js index 0b5c36d..6ad259c 100644 --- a/src/routes.js +++ b/src/routes.js @@ -2,14 +2,9 @@ import { HashRouter, Route } from 'react-router-dom'; import TradePage from './pages/TradePage'; import OpenOrdersPage from './pages/OpenOrdersPage'; import React from 'react'; -import { Layout } from 'antd'; -import TopBar from './components/TopBar'; -import { CustomFooter } from './components/Footer'; import BalancesPage from './pages/BalancesPage'; import BasicLayout from './components/BasicLayout'; -const { Header, Content } = Layout; - export function Routes() { return ( diff --git a/src/utils/connection.js b/src/utils/connection.js index 8096d45..e4a41ac 100644 --- a/src/utils/connection.js +++ b/src/utils/connection.js @@ -80,7 +80,7 @@ export function useAccountInfo(publicKey) { let id = publicKey?.toBase58(); useEffect(() => { if (!publicKey) { - return () => {}; + return; } if (accountListenerCount.has(cacheKey)) { let currentItem = accountListenerCount.get(cacheKey); diff --git a/src/utils/markets.js b/src/utils/markets.js index 9ed4148..d571450 100644 --- a/src/utils/markets.js +++ b/src/utils/markets.js @@ -3,15 +3,17 @@ import { Orderbook, decodeEventQueue, DEX_PROGRAM_ID, + TokenInstructions, } from '@project-serum/serum'; import React, { useContext, useEffect, useState } from 'react'; import { PublicKey } from '@solana/web3.js'; import { useLocalStorageState } from './utils'; import { useAsyncData } from './fetch-loop'; -import { useAccountData, useConnection } from './connection'; +import { useAccountData, useAccountInfo, useConnection } from './connection'; import { useWallet } from './wallet'; import tuple from 'immutable-tuple'; import { notify } from './notifications'; +import { BN } from 'bn.js'; const DEFAULT_MARKET_NAME = 'SRM/USDT'; @@ -26,17 +28,10 @@ export const COIN_MINTS = { BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW: 'USDC', MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L: 'MSRM', SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt: 'SRM', + [TokenInstructions.WRAPPED_SOL_MINT]: 'SOL', }; export const MARKET_INFO_BY_NAME = { - 'MSRM/USDT': { - address: 'H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ', - name: 'MSRM/USDT', - }, - 'MSRM/USDC': { - address: '7kgkDyW7dmyMeP8KFXzbcUZz1R2WHsovDZ7n3ihZuNDS', - name: 'MSRM/USDC', - }, 'BTC/USDT': { address: '8AcVjMG2LTbpkjNoyq8RwysokqZunkjy3d5JDzxC6BJa', name: 'BTC/USDT', @@ -53,6 +48,16 @@ export const MARKET_INFO_BY_NAME = { address: 'ASKiV944nKg1W9vsf7hf3fTsjawK6DwLwrnB2LH9n61c', name: 'ETH/USDC', }, + 'SOL/USDT': { + address: '8mDuvJJSgoodovMRYArtVVYBbixWYdGzR47GPrRT65YJ', + name: 'SOL/USDT', + programId: 'BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg', + }, + 'SOL/USDC': { + address: 'Cdp72gDcYMCLLk3aDkPxjeiirKoFqK38ECm8Ywvk94Wi', + name: 'SOL/USDC', + programId: 'BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg', + }, 'SRM/USDT': { address: 'HARFLhSq8nECZk4DVFKvzqXMNMA9a3hjvridGMFizeLa', name: 'SRM/USDT', @@ -61,6 +66,14 @@ export const MARKET_INFO_BY_NAME = { address: '68J6nkWToik6oM9rTatKSR5ibVSykAtzftBUEAvpRsys', name: 'SRM/USDC', }, + 'MSRM/USDT': { + address: 'H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ', + name: 'MSRM/USDT', + }, + 'MSRM/USDC': { + address: '7kgkDyW7dmyMeP8KFXzbcUZz1R2WHsovDZ7n3ihZuNDS', + name: 'MSRM/USDC', + }, 'FTT/USDT': { address: 'DHDdghmkBhEpReno3tbzBPtsxCt6P3KrMzZvxavTktJt', name: 'FTT/USDT', @@ -329,6 +342,7 @@ export function useBaseCurrencyAccounts() { return await market.findBaseTokenAccountsForOwner( connection, wallet.publicKey, + true, ); } return useAsyncData( @@ -353,6 +367,7 @@ export function useQuoteCurrencyAccounts() { return await market.findQuoteTokenAccountsForOwner( connection, wallet.publicKey, + true, ); } return useAsyncData( @@ -380,50 +395,30 @@ export function useSelectedBaseCurrencyAccount() { // TODO: Update to use websocket export function useQuoteCurrencyBalances() { - const connection = useConnection(); const quoteCurrencyAccount = useSelectedQuoteCurrencyAccount(); - async function getBalance() { - if (!quoteCurrencyAccount) { - return null; - } - const balances = await connection.getTokenAccountBalance( - quoteCurrencyAccount.pubkey, - ); - return balances && balances.value && balances.value.uiAmount; + const { market } = useMarket(); + const [accountInfo, loaded] = useAccountInfo(quoteCurrencyAccount?.pubkey); + if (!market || !quoteCurrencyAccount || !loaded) { + return null; } - return useAsyncData( - getBalance, - tuple( - 'useQuoteCurrencyBalances', - connection, - quoteCurrencyAccount && quoteCurrencyAccount.pubkey.toBase58(), - ), - { refreshInterval: _FAST_REFRESH_INTERVAL }, - ); + if (market.quoteMintAddress.equals(TokenInstructions.WRAPPED_SOL_MINT)) { + return accountInfo?.lamports / 1e9 ?? 0; + } + return market.quoteSplSizeToNumber(new BN(accountInfo.data.slice(64, 72))); } // TODO: Update to use websocket export function useBaseCurrencyBalances() { - const connection = useConnection(); const baseCurrencyAccount = useSelectedBaseCurrencyAccount(); - async function getBalance() { - if (!baseCurrencyAccount) { - return null; - } - const balances = await connection.getTokenAccountBalance( - baseCurrencyAccount.pubkey, - ); - return balances && balances.value && balances.value.uiAmount; + const { market } = useMarket(); + const [accountInfo, loaded] = useAccountInfo(baseCurrencyAccount?.pubkey); + if (!market || !baseCurrencyAccount || !loaded) { + return null; } - return useAsyncData( - getBalance, - tuple( - 'useBaseCurrencyBalances', - connection, - baseCurrencyAccount && baseCurrencyAccount.pubkey.toBase58(), - ), - { refreshInterval: _FAST_REFRESH_INTERVAL }, - ); + if (market.baseMintAddress.equals(TokenInstructions.WRAPPED_SOL_MINT)) { + return accountInfo?.lamports / 1e9 ?? 0; + } + return market.baseSplSizeToNumber(new BN(accountInfo.data.slice(64, 72))); } export function useOpenOrders() { @@ -583,8 +578,8 @@ export function useOpenOrdersForAllMarkets() { } export function useBalances() { - const [baseCurrencyBalances] = useBaseCurrencyBalances(); - const [quoteCurrencyBalances] = useQuoteCurrencyBalances(); + const baseCurrencyBalances = useBaseCurrencyBalances(); + const quoteCurrencyBalances = useQuoteCurrencyBalances(); const openOrdersAccount = useSelectedOpenOrdersAccount(true); const { baseCurrency, quoteCurrency, market } = useMarket(); const baseExists = diff --git a/src/utils/send.js b/src/utils/send.js index 011b6eb..c556b37 100644 --- a/src/utils/send.js +++ b/src/utils/send.js @@ -1,8 +1,5 @@ import { notify } from './notifications'; -import nacl from 'tweetnacl'; -import { sleep, getDecimalCount } from './utils'; -import { Transaction, PublicKey } from '@solana/web3.js'; -import { Buffer } from 'buffer'; +import { getDecimalCount, sleep } from './utils'; export async function settleFunds({ market, @@ -34,25 +31,12 @@ export async function settleFunds({ return; } - // This is a workaround for this issue: https://github.com/solana-labs/solana-web3.js/issues/985 - const transaction = new Transaction(); - const vaultSigner = - market.address.toBase58() === 'H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ' - ? new PublicKey('12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA') - : await PublicKey.createProgramAddress( - [ - market.address.toBuffer(), - market._decoded.vaultSignerNonce.toArrayLike(Buffer, 'le', 8), - ], - market._programId, - ); - const settleInstruction = market.makeSettleInstruction( + const { transaction, signers } = await market.makeSettleFundsTransaction( + connection, openOrders, baseCurrencyAccount.pubkey, quoteCurrencyAccount.pubkey, - vaultSigner, ); - transaction.add(settleInstruction); const onConfirm = (result) => { if (result.timeout) { @@ -73,6 +57,7 @@ export async function settleFunds({ notify({ message: 'Funds settled', type: 'success' }); return await sendTransaction({ transaction, + signers, wallet, connection, onBeforeSend, @@ -94,14 +79,13 @@ export async function cancelOrders({ onAfterSendCallBack, onConfirmCallBack, }) { - const transaction = new Transaction(); - transaction.add(market.makeMatchOrdersInstruction(5)); + const transaction = market.makeMatchOrdersTransaction(5); orders.forEach((order) => { transaction.add( market.makeCancelOrderInstruction(connection, wallet.publicKey, order), ); }); - transaction.add(market.makeMatchOrdersInstruction(5)); + transaction.add(market.makeMatchOrdersTransaction(5)); const onConfirm = (result) => { if (result.timeout) { notify({ @@ -161,7 +145,6 @@ export async function placeOrder({ wallet, baseCurrencyAccount, quoteCurrencyAccount, - openOrdersAccount, onBeforeSendCallBack, onAfterSendCallBack, onConfirmCallBack, @@ -231,26 +214,16 @@ export async function placeOrder({ size, orderType, }; - let transaction, signers; - let extraSigners = []; + console.log(params); - // If the user does not has an open orders account, use serum-js to create one - if (!openOrdersAccount) { - let result = await market.makePlaceOrderTransaction(connection, params); - transaction = result.transaction; - signers = result.signers; - if (signers.length > 1) { - extraSigners = [signers[1]]; - } - } else { - transaction = new Transaction(); - transaction.add(market.makeMatchOrdersInstruction(5)); - transaction.add( - market.makePlaceOrderInstruction(connection, params, openOrdersAccount), - ); - } + const transaction = market.makeMatchOrdersTransaction(5); + let { + transaction: placeOrderTx, + signers, + } = await market.makePlaceOrderTransaction(connection, params); + transaction.add(placeOrderTx); + transaction.add(market.makeMatchOrdersTransaction(5)); - transaction.add(market.makeMatchOrdersInstruction(5)); const onConfirm = (result) => { if (result.timeout) { notify({ @@ -282,34 +255,26 @@ export async function placeOrder({ onBeforeSend, onAfterSend, onConfirm, - extraSigners, + signers, }); } async function sendTransaction({ transaction, wallet, + signers = [wallet.publicKey], connection, onBeforeSend, onAfterSend, onConfirm, - extraSigners = [], }) { transaction.recentBlockhash = ( await connection.getRecentBlockhash('max') ).blockhash; + transaction.signPartial(...signers); const signed = await wallet.signTransaction(transaction); const signedAt = new Date().getTime(); - // Don't rely on the open orders account being the 2nd element in the list - // Sign with any accounts with a pubkey different from that of the wallet - extraSigners.forEach((extraSigner) => { - const extraSignature = nacl.sign.detached( - signed.serializeMessage(), - extraSigner.secretKey, - ); - signed.addSignature(extraSigner.publicKey, extraSignature); - }); onBeforeSend(); const txid = await connection.sendRawTransaction(signed.serialize(), { diff --git a/yarn.lock b/yarn.lock index 1141e8c..1d386c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1467,10 +1467,10 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== -"@project-serum/serum@^0.9.2": - version "0.9.2" - resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.9.2.tgz#72fa9855d9bdef169e20d01f838d74361ce11c81" - integrity sha512-EcDQUTaMigREhalhvzRVwIjN/54QuHhDFq81d3cML90T/Ac71kh5b7PdUNG9eyE9CsdvD1vLuW9cXrEUj5YnEw== +"@project-serum/serum@^0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.11.1.tgz#4d41bdc49e30d82168af5b4d4c8773a1b670ac81" + integrity sha512-/bYJEwlZ3dFINg5YsaD5rK619YA+Qgbx7tR54vJ6X3AJun9GZCNCubfgD3blOCyy5PnhxBB/pchv/oYT5cXgMg== dependencies: "@solana/web3.js" "^0.71.9" bn.js "^5.1.2"