Auto create associated token account on withdrawal (#18)

* Auto create associated token account on withdrawal
* simplify withdraw and default to USDC
This commit is contained in:
Maximilian Schneider 2021-05-27 16:40:11 +03:00 committed by GitHub
parent da7af24d2b
commit 7558165c27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 32 deletions

View File

@ -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 (
<Modal isOpen={isOpen} onClose={onClose}>

View File

@ -203,7 +203,7 @@ const useMangoStore = create<MangoStore>((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,

80
utils/associated.tsx Normal file
View File

@ -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<PublicKey> {
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<TransactionInstruction> {
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([]),
})
}

View File

@ -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<TransactionSignature> {
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<TransactionSignature> {
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,