diff --git a/components/WithdrawModal.tsx b/components/WithdrawModal.tsx index 67ad3cf..bc14ab0 100644 --- a/components/WithdrawModal.tsx +++ b/components/WithdrawModal.tsx @@ -32,7 +32,7 @@ import { MarginAccount, uiToNative } from '@blockworks-foundation/mango-client' import Select from './Select' const WithdrawModal = ({ isOpen, onClose }) => { - const [withdrawTokenSymbol, setWithdrawTokenSymbol] = useState('') + const [withdrawTokenSymbol, setWithdrawTokenSymbol] = useState('USDC') const [inputAmount, setInputAmount] = useState(0) const [invalidAmountMessage, setInvalidAmountMessage] = useState('') const [maxAmount, setMaxAmount] = useState(0) @@ -44,21 +44,12 @@ const WithdrawModal = ({ isOpen, onClose }) => { const [maxButtonTransition, setMaxButtonTransition] = useState(false) const { getTokenIndex, symbols } = useMarketList() const { connection, programId } = useConnection() - const walletAccounts = useMangoStore((s) => s.wallet.balances) const prices = useMangoStore((s) => s.selectedMangoGroup.prices) const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const selectedMarginAccount = useMangoStore( (s) => s.selectedMarginAccount.current ) const actions = useMangoStore((s) => s.actions) - const withdrawAccounts = useMemo( - () => - walletAccounts.filter((acc) => - Object.values(symbols).includes(acc.account.mint.toString()) - ), - [symbols, walletAccounts] - ) - const [selectedAccount, setSelectedAccount] = useState(withdrawAccounts[0]) const tokenIndex = useMemo( () => getTokenIndex(symbols[withdrawTokenSymbol]), [withdrawTokenSymbol, getTokenIndex] @@ -130,6 +121,7 @@ const WithdrawModal = ({ isOpen, onClose }) => { prices ) const leverage = 1 / Math.max(0, collateralRatio - 1) + setSimulation({ equity, assetsVal, @@ -160,8 +152,7 @@ const WithdrawModal = ({ isOpen, onClose }) => { mangoGroup, marginAccount, wallet, - selectedAccount.account.mint, - selectedAccount.publicKey, + new PublicKey(symbols[withdrawTokenSymbol]), Number(inputAmount) ) .then((_transSig: string) => { @@ -188,8 +179,7 @@ const WithdrawModal = ({ isOpen, onClose }) => { mangoGroup, marginAccount, wallet, - selectedAccount.account.mint, - selectedAccount.publicKey, + new PublicKey(symbols[withdrawTokenSymbol]), Number(inputAmount) ) .then((_transSig: string) => { @@ -343,7 +333,7 @@ const WithdrawModal = ({ isOpen, onClose }) => { } }, [withdrawTokenSymbol]) - if (!selectedAccount) return null + if (!withdrawTokenSymbol) return null return ( diff --git a/stores/useMangoStore.tsx b/stores/useMangoStore.tsx index fde5013..e0deae6 100644 --- a/stores/useMangoStore.tsx +++ b/stores/useMangoStore.tsx @@ -203,7 +203,7 @@ const useMangoStore = create((set, get) => ({ const mangoClient = get().mangoClient const set = get().set - if (wallet?.publicKey && connected) { + if (wallet?.publicKey && connected && selectedMangoGroup) { const usersMangoSrmAccounts = await mangoClient.getMangoSrmAccountsForOwner( connection, diff --git a/utils/associated.tsx b/utils/associated.tsx new file mode 100644 index 0000000..8453cb4 --- /dev/null +++ b/utils/associated.tsx @@ -0,0 +1,80 @@ +import { + PublicKey, + SystemProgram, + SYSVAR_RENT_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' + +const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID: PublicKey = new PublicKey( + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' +) + +export async function findAssociatedTokenAddress( + walletAddress: PublicKey, + tokenMintAddress: PublicKey +): Promise { + return ( + await PublicKey.findProgramAddress( + [ + walletAddress.toBuffer(), + TOKEN_PROGRAM_ID.toBuffer(), + tokenMintAddress.toBuffer(), + ], + SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID + ) + )[0] +} + +export async function createAssociatedTokenAccount( + fundingAddress: PublicKey, + walletAddress: PublicKey, + splTokenMintAddress: PublicKey +): Promise { + const associatedTokenAddress = await findAssociatedTokenAddress( + walletAddress, + splTokenMintAddress + ) + const keys = [ + { + pubkey: fundingAddress, + isSigner: true, + isWritable: true, + }, + { + pubkey: associatedTokenAddress, + isSigner: false, + isWritable: true, + }, + { + pubkey: walletAddress, + isSigner: false, + isWritable: false, + }, + { + pubkey: splTokenMintAddress, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false, + }, + ] + return new TransactionInstruction({ + keys, + programId: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, + data: Buffer.from([]), + }) +} diff --git a/utils/mango.tsx b/utils/mango.tsx index 528e890..48d167c 100644 --- a/utils/mango.tsx +++ b/utils/mango.tsx @@ -52,6 +52,10 @@ import { } from '@project-serum/serum/lib/token-instructions' import { MangoSrmAccount } from '@blockworks-foundation/mango-client/lib/client' import { capitalize } from './index' +import { + findAssociatedTokenAddress, + createAssociatedTokenAccount, +} from './associated' export const DEFAULT_MANGO_GROUP = 'BTC_ETH_SOL_SRM_USDC' @@ -348,21 +352,21 @@ export async function withdraw( marginAccount: MarginAccount, wallet: Wallet, token: PublicKey, - tokenAcc: PublicKey, - quantity: number ): Promise { const transaction = new Transaction() const signers = [] + let tokenAcc = await findAssociatedTokenAddress(wallet.publicKey, token) let wrappedSolAccount: Account | null = null if (token.equals(WRAPPED_SOL_MINT)) { wrappedSolAccount = new Account() + tokenAcc = wrappedSolAccount.publicKey const lamports = Math.round(quantity * LAMPORTS_PER_SOL) + 1e7 transaction.add( SystemProgram.createAccount({ fromPubkey: wallet.publicKey, - newAccountPubkey: wrappedSolAccount.publicKey, + newAccountPubkey: tokenAcc, lamports, space: 165, programId: TOKEN_PROGRAM_ID, @@ -370,12 +374,23 @@ export async function withdraw( ) transaction.add( initializeAccount({ - account: wrappedSolAccount.publicKey, + account: tokenAcc, mint: WRAPPED_SOL_MINT, owner: wallet.publicKey, }) ) signers.push(wrappedSolAccount) + } else { + const tokenAccExists = await connection.getAccountInfo(tokenAcc, 'recent') + if (!tokenAccExists) { + transaction.add( + await createAssociatedTokenAccount( + wallet.publicKey, + wallet.publicKey, + token + ) + ) + } } const tokenIndex = mangoGroup.getTokenIndex(token) @@ -391,7 +406,7 @@ export async function withdraw( { isSigner: false, isWritable: true, - pubkey: wrappedSolAccount?.publicKey ?? tokenAcc, + pubkey: tokenAcc, }, { isSigner: false, @@ -448,21 +463,21 @@ export async function borrowAndWithdraw( marginAccount: MarginAccount, wallet: Wallet, token: PublicKey, - tokenAcc: PublicKey, - withdrawQuantity: number ): Promise { const transaction = new Transaction() const signers = [] + let tokenAcc = await findAssociatedTokenAddress(wallet.publicKey, token) let wrappedSolAccount: Account | null = null if (token.equals(WRAPPED_SOL_MINT)) { wrappedSolAccount = new Account() + tokenAcc = wrappedSolAccount.publicKey const lamports = Math.round(withdrawQuantity * LAMPORTS_PER_SOL) + 1e7 transaction.add( SystemProgram.createAccount({ fromPubkey: wallet.publicKey, - newAccountPubkey: wrappedSolAccount.publicKey, + newAccountPubkey: tokenAcc, lamports, space: 165, programId: TOKEN_PROGRAM_ID, @@ -476,6 +491,17 @@ export async function borrowAndWithdraw( }) ) signers.push(wrappedSolAccount) + } else { + const tokenAccExists = await connection.getAccountInfo(tokenAcc, 'recent') + if (!tokenAccExists) { + transaction.add( + await createAssociatedTokenAccount( + wallet.publicKey, + wallet.publicKey, + token + ) + ) + } } const tokenIndex = mangoGroup.getTokenIndex(token) @@ -839,10 +865,11 @@ export async function placeOrderAndSettle( ) { // open orders missing for this market; create a new one now const openOrdersSpace = OpenOrders.getLayout(mangoGroup.dexProgramId).span - const openOrdersLamports = await connection.getMinimumBalanceForRentExemption( - openOrdersSpace, - 'singleGossip' - ) + const openOrdersLamports = + await connection.getMinimumBalanceForRentExemption( + openOrdersSpace, + 'singleGossip' + ) const accInstr = await createAccountInstruction( connection, wallet.publicKey, @@ -1057,10 +1084,11 @@ export async function placeAndSettle( ) { // open orders missing for this market; create a new one now const openOrdersSpace = OpenOrders.getLayout(mangoGroup.dexProgramId).span - const openOrdersLamports = await connection.getMinimumBalanceForRentExemption( - openOrdersSpace, - 'singleGossip' - ) + const openOrdersLamports = + await connection.getMinimumBalanceForRentExemption( + openOrdersSpace, + 'singleGossip' + ) const accInstr = await createAccountInstruction( connection, wallet.publicKey,