Allow SPL WUSDC -> SPL USDC swaps

This commit is contained in:
Gary Wang 2020-10-21 10:53:11 -07:00
parent 896a36bb2e
commit eac4ae3c8d
4 changed files with 99 additions and 26 deletions

View File

@ -5,7 +5,7 @@
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@project-serum/serum": "^0.13.5", "@project-serum/serum": "^0.13.6",
"@solana/web3.js": "^0.78.2", "@solana/web3.js": "^0.78.2",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",

View File

@ -5,7 +5,7 @@ import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from '@material-ui/core/DialogContent';
import TextField from '@material-ui/core/TextField'; import TextField from '@material-ui/core/TextField';
import DialogForm from './DialogForm'; import DialogForm from './DialogForm';
import { useWallet } from '../utils/wallet'; import { useWallet, useWalletAddressForMint } from '../utils/wallet';
import { PublicKey } from '@solana/web3.js'; import { PublicKey } from '@solana/web3.js';
import { abbreviateAddress } from '../utils/utils'; import { abbreviateAddress } from '../utils/utils';
import InputAdornment from '@material-ui/core/InputAdornment'; import InputAdornment from '@material-ui/core/InputAdornment';
@ -29,9 +29,14 @@ import Typography from '@material-ui/core/Typography';
import { useAsyncData } from '../utils/fetch-loop'; import { useAsyncData } from '../utils/fetch-loop';
import CircularProgress from '@material-ui/core/CircularProgress'; import CircularProgress from '@material-ui/core/CircularProgress';
const WUSDC_MINT = new PublicKey(
'BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW',
);
const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
export default function SendDialog({ open, onClose, publicKey, balanceInfo }) { export default function SendDialog({ open, onClose, publicKey, balanceInfo }) {
const isProdNetwork = useIsProdNetwork(); const isProdNetwork = useIsProdNetwork();
const [tab, setTab] = useState(0); const [tab, setTab] = useState('spl');
const onSubmitRef = useRef(); const onSubmitRef = useRef();
const [swapCoinInfo] = useSwapApiGet( const [swapCoinInfo] = useSwapApiGet(
@ -62,23 +67,52 @@ export default function SendDialog({ open, onClose, publicKey, balanceInfo }) {
textColor="primary" textColor="primary"
indicatorColor="primary" indicatorColor="primary"
> >
<Tab label={`SPL ${swapCoinInfo.ticker}`} /> {mint?.equals(WUSDC_MINT)
<Tab ? [
label={`${swapCoinInfo.erc20Contract ? 'ERC20' : 'Native'} ${ <Tab label="SPL WUSDC" key="spl" value="spl" />,
swapCoinInfo.ticker <Tab
}`} label="SPL USDC"
/> key="wusdcToSplUsdc"
value="wusdcToSplUsdc"
/>,
<Tab label="ERC20 USDC" key="swap" value="swap" />,
]
: [
<Tab
label={`SPL ${swapCoinInfo.ticker}`}
key="spl"
value="spl"
/>,
<Tab
label={`${
swapCoinInfo.erc20Contract ? 'ERC20' : 'Native'
} ${swapCoinInfo.ticker}`}
key="swap"
value="swap"
/>,
]}
</Tabs> </Tabs>
) : null} ) : null}
{tab === 0 ? ( {tab === 'spl' ? (
<SendSplDialog <SendSplDialog
onClose={onClose} onClose={onClose}
publicKey={publicKey} publicKey={publicKey}
balanceInfo={balanceInfo} balanceInfo={balanceInfo}
onSubmitRef={onSubmitRef} onSubmitRef={onSubmitRef}
/> />
) : tab === 'wusdcToSplUsdc' ? (
<SendSwapDialog
key={tab}
onClose={onClose}
publicKey={publicKey}
balanceInfo={balanceInfo}
swapCoinInfo={swapCoinInfo}
onSubmitRef={onSubmitRef}
wusdcToSplUsdc
/>
) : ( ) : (
<SendSwapDialog <SendSwapDialog
key={tab}
onClose={onClose} onClose={onClose}
publicKey={publicKey} publicKey={publicKey}
balanceInfo={balanceInfo} balanceInfo={balanceInfo}
@ -146,6 +180,7 @@ function SendSwapDialog({
balanceInfo, balanceInfo,
swapCoinInfo, swapCoinInfo,
ethAccount, ethAccount,
wusdcToSplUsdc = false,
onSubmitRef, onSubmitRef,
}) { }) {
const wallet = useWallet(); const wallet = useWallet();
@ -160,8 +195,11 @@ function SendSwapDialog({
} = useForm(balanceInfo); } = useForm(balanceInfo);
const { tokenName, decimals, mint } = balanceInfo; const { tokenName, decimals, mint } = balanceInfo;
const blockchain = const blockchain = wusdcToSplUsdc
swapCoinInfo.blockchain === 'sol' ? 'eth' : swapCoinInfo.blockchain; ? 'sol'
: swapCoinInfo.blockchain === 'sol'
? 'eth'
: swapCoinInfo.blockchain;
const needMetamask = blockchain === 'eth'; const needMetamask = blockchain === 'eth';
useEffect(() => { useEffect(() => {
@ -170,19 +208,34 @@ function SendSwapDialog({
} }
}, [blockchain, ethAccount, setDestinationAddress]); }, [blockchain, ethAccount, setDestinationAddress]);
let splUsdcWalletAddress = useWalletAddressForMint(
wusdcToSplUsdc ? USDC_MINT : null,
);
useEffect(() => {
if (wusdcToSplUsdc && splUsdcWalletAddress) {
setDestinationAddress(splUsdcWalletAddress);
}
}, [setDestinationAddress, wusdcToSplUsdc, splUsdcWalletAddress]);
async function makeTransaction() { async function makeTransaction() {
let amount = Math.round(parseFloat(transferAmountString) * 10 ** decimals); let amount = Math.round(parseFloat(transferAmountString) * 10 ** decimals);
if (!amount || amount <= 0) { if (!amount || amount <= 0) {
throw new Error('Invalid amount'); throw new Error('Invalid amount');
} }
const swapInfo = await swapApiRequest('POST', 'swap_to', { const params = {
blockchain, blockchain,
coin: swapCoinInfo.erc20Contract,
address: destinationAddress, address: destinationAddress,
size: amount / 10 ** decimals, size: amount / 10 ** decimals,
wusdcToUsdc: };
mint?.toBase58() === 'BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW', if (blockchain === 'sol') {
}); params.coin = swapCoinInfo.splMint;
} else if (blockchain === 'eth') {
params.coin = swapCoinInfo.erc20Contract;
}
if (mint?.equals(WUSDC_MINT)) {
params.wusdcToUsdc = true;
}
const swapInfo = await swapApiRequest('POST', 'swap_to', params);
if (swapInfo.blockchain !== 'sol') { if (swapInfo.blockchain !== 'sol') {
throw new Error('Unexpected blockchain'); throw new Error('Unexpected blockchain');
} }
@ -205,6 +258,7 @@ function SendSwapDialog({
key={signature} key={signature}
publicKey={publicKey} publicKey={publicKey}
signature={signature} signature={signature}
blockchain={blockchain}
onClose={onClose} onClose={onClose}
/> />
); );
@ -215,7 +269,11 @@ function SendSwapDialog({
<DialogContent style={{ paddingTop: 16 }}> <DialogContent style={{ paddingTop: 16 }}>
<DialogContentText> <DialogContentText>
SPL {tokenName} can be converted to{' '} SPL {tokenName} can be converted to{' '}
{swapCoinInfo.erc20Contract ? 'ERC20' : 'native'}{' '} {blockchain === 'eth' && swapCoinInfo.erc20Contract
? 'ERC20'
: blockchain === 'sol' && swapCoinInfo.splMint
? 'SPL'
: 'native'}{' '}
{swapCoinInfo.ticker} {swapCoinInfo.ticker}
{needMetamask ? ' via MetaMask' : null}. {needMetamask ? ' via MetaMask' : null}.
</DialogContentText> </DialogContentText>
@ -235,7 +293,7 @@ function SendSwapDialog({
); );
} }
function SendSwapProgress({ publicKey, signature, onClose }) { function SendSwapProgress({ publicKey, signature, onClose, blockchain }) {
const connection = useConnection(); const connection = useConnection();
const [swaps] = useSwapApiGet(`swaps_from/sol/${publicKey.toBase58()}`, { const [swaps] = useSwapApiGet(`swaps_from/sol/${publicKey.toBase58()}`, {
refreshInterval: 1000, refreshInterval: 1000,
@ -257,6 +315,8 @@ function SendSwapProgress({ publicKey, signature, onClose }) {
if (withdrawal.txid?.startsWith('0x')) { if (withdrawal.txid?.startsWith('0x')) {
step = 3; step = 3;
ethTxid = withdrawal.txid; ethTxid = withdrawal.txid;
} else if (withdrawal.txid && blockchain !== 'eth') {
step = 3;
} else { } else {
step = 2; step = 2;
} }
@ -287,7 +347,7 @@ function SendSwapProgress({ publicKey, signature, onClose }) {
View on Etherscan View on Etherscan
</Link> </Link>
</Typography> </Typography>
) : ( ) : step < 3 ? (
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@ -304,8 +364,8 @@ function SendSwapProgress({ publicKey, signature, onClose }) {
<Typography>Transaction Pending</Typography> <Typography>Transaction Pending</Typography>
)} )}
</div> </div>
)} ) : null}
{!ethTxid ? ( {!ethTxid && blockchain === 'eth' ? (
<DialogContentText style={{ marginTop: 16, marginBottom: 0 }}> <DialogContentText style={{ marginTop: 16, marginBottom: 0 }}>
Please keep this window open. You will need to approve the request Please keep this window open. You will need to approve the request
on MetaMask to complete the transaction. on MetaMask to complete the transaction.

View File

@ -165,6 +165,19 @@ export function refreshWalletPublicKeys(wallet) {
refreshCache(wallet.getTokenAccountInfo); refreshCache(wallet.getTokenAccountInfo);
} }
export function useWalletAddressForMint(mint) {
const [walletAccounts] = useWalletTokenAccounts();
return useMemo(
() =>
mint
? walletAccounts
?.find((account) => account.parsed?.mint?.equals(mint))
?.publicKey.toBase58()
: null,
[walletAccounts, mint],
);
}
export function useBalanceInfo(publicKey) { export function useBalanceInfo(publicKey) {
let [accountInfo, accountInfoLoaded] = useAccountInfo(publicKey); let [accountInfo, accountInfoLoaded] = useAccountInfo(publicKey);
let { mint, owner, amount } = accountInfo?.owner.equals(TOKEN_PROGRAM_ID) let { mint, owner, amount } = accountInfo?.owner.equals(TOKEN_PROGRAM_ID)

View File

@ -1578,10 +1578,10 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
"@project-serum/serum@^0.13.5": "@project-serum/serum@^0.13.6":
version "0.13.5" version "0.13.6"
resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.5.tgz#9e8591886b3fc7f2486a94c242e5728d97da0938" resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.6.tgz#d45fa52a31d2820d19030964fd3a418e87b8a1f9"
integrity sha512-qMnxYKR/z8pqN+LFUIPRFwdSsJ1bGD39Jy6nooQl+aFp+ZpS8+hjIsJQGjpJA4kf+H+U8v6kcMhgMJ6diAWUHw== integrity sha512-DoiqJABmGKWvrOLI0/ZctnXlzsPxIiKVHeIsFua3ODXqFWSDojUQDs8y1Rd26wFPEqB6Vsq1Xq8sqsku1leziQ==
dependencies: dependencies:
"@solana/web3.js" "^0.71.10" "@solana/web3.js" "^0.71.10"
bn.js "^5.1.2" bn.js "^5.1.2"