mango-token-sale/stores/useWalletStore.tsx

374 lines
11 KiB
TypeScript

import create, { State } from 'zustand'
import produce from 'immer'
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
import * as anchor from '@project-serum/anchor'
import { EndpointInfo, WalletAdapter } from '../@types/types'
// @ts-ignore
import poolIdl from '../idls/ido_pool'
import {
getOwnedTokenAccounts,
getMint,
ProgramAccount,
TokenAccount,
MintAccount,
getTokenAccount,
} from '../utils/tokens'
import { findLargestBalanceAccountForMint } from '../hooks/useLargestAccounts'
import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { createAssociatedTokenAccount } from '../utils/associated'
import { sendTransaction } from '../utils/send'
import { DEFAULT_PROVIDER } from '../utils/wallet-adapters'
export const ENDPOINTS: EndpointInfo[] = [
{
name: 'mainnet-beta',
url: 'https://api.mainnet-beta.solana.com/',
websocket: 'https://api.mainnet-beta.solana.com/',
programId: '',
poolKey: '',
},
{
name: 'devnet',
url: 'https://api.devnet.solana.com',
websocket: 'https://api.devnet.solana.com',
programId: '2oBtRS2AAQfsMxXQfg41fKFY9zjvHwSSD7G5idrCFziV',
poolKey: '44B8GA669DfdT3PrdxPmsHfGyWrwhKuzLdXBXV8oNdPp',
},
{
name: 'localnet',
url: 'http://localhost:8899',
websocket: 'http://localhost:8899',
programId: 'FF8zcQ1aEmyXeBt99hohoyYprgpEVmWsRK44qta3emno',
poolKey: '8gswb9g1JdYEVj662KXr9p6p9SMgR77NryyqvWn9GPXJ',
},
]
const CLUSTER = 'devnet'
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER)
const DEFAULT_CONNECTION = new Connection(ENDPOINT.url, 'recent')
const WEBSOCKET_CONNECTION = new Connection(ENDPOINT.websocket, 'recent')
const PROGRAM_ID = new PublicKey(ENDPOINT.programId)
const POOL_PK = new PublicKey(ENDPOINT.poolKey)
interface PoolAccount {
distributionAuthority: PublicKey
endDepositsTs: anchor.BN
endIdoTs: anchor.BN
nonce: number
numIdoTokens: anchor.BN
poolUsdc: PublicKey
poolWatermelon: PublicKey
redeemableMint: PublicKey
startIdoTs: anchor.BN
watermelonMint: PublicKey
}
interface WalletStore extends State {
connected: boolean
connection: {
cluster: string
current: Connection
websocket: Connection
endpoint: string
programId: PublicKey
}
current: WalletAdapter | undefined
providerUrl: string
provider: anchor.Provider | undefined
program: anchor.Program | undefined
pool: PoolAccount | undefined
mangoVault: TokenAccount | undefined
usdcVault: TokenAccount | undefined
tokenAccounts: ProgramAccount<TokenAccount>[]
mints: { [pubkey: string]: MintAccount }
set: (x: any) => void
actions: any
}
const useWalletStore = create<WalletStore>((set, get) => ({
connected: false,
connection: {
cluster: CLUSTER,
current: DEFAULT_CONNECTION,
websocket: WEBSOCKET_CONNECTION,
endpoint: ENDPOINT.url,
programId: PROGRAM_ID,
},
current: null,
providerUrl: DEFAULT_PROVIDER.url,
provider: undefined,
program: undefined,
pool: undefined,
mangoVault: undefined,
usdcVault: undefined,
tokenAccounts: [],
mints: {},
actions: {
async fetchPool() {
const connection = get().connection.current
const wallet = get().current
const programId = get().connection.programId
const set = get().set
// console.log('fetchPool', connection, poolIdl)
if (connection) {
const provider = new anchor.Provider(
connection,
wallet,
anchor.Provider.defaultOptions()
)
const program = new anchor.Program(poolIdl, programId, provider)
const pool = (await program.account.poolAccount.fetch(
POOL_PK
)) as PoolAccount
const [usdcVault, mangoVault] = await Promise.all([
getTokenAccount(connection, pool.poolUsdc),
getTokenAccount(connection, pool.poolWatermelon),
])
// console.log('fetchPool', { program, pool, usdcVault, mangoVault })
set((state) => {
state.provider = provider
state.program = program
state.pool = pool
state.usdcVault = usdcVault.account
state.mangoVault = mangoVault.account
})
}
},
async fetchWalletTokenAccounts() {
const connection = get().connection.current
const connected = get().connected
const wallet = get().current
const walletOwner = wallet?.publicKey
const set = get().set
console.log(
'fetchWalletTokenAccounts',
connected,
walletOwner?.toString()
)
if (connected && walletOwner) {
const ownedTokenAccounts = await getOwnedTokenAccounts(
connection,
walletOwner
)
set((state) => {
state.tokenAccounts = ownedTokenAccounts
})
} else {
set((state) => {
state.tokenAccounts = []
})
}
},
async fetchUsdcVault() {
const connection = get().connection.current
const pool = get().pool
const set = get().set
if (!pool) return
const { account: vault } = await getTokenAccount(
connection,
pool.poolUsdc
)
console.log('fetchUsdcVault', vault)
set((state) => {
state.usdcVault = vault
})
},
async fetchMints() {
const connection = get().connection.current
const pool = get().pool
const mangoVault = get().mangoVault
const usdcVault = get().usdcVault
const set = get().set
const mintKeys = [mangoVault.mint, usdcVault.mint, pool.redeemableMint]
const mints = await Promise.all(
mintKeys.map((pk) => getMint(connection, pk))
)
// console.log('fetchMints', mints)
set((state) => {
for (const pa of mints) {
state.mints[pa.publicKey.toBase58()] = pa.account
// console.log('mint', pa.publicKey.toBase58(), pa.account)
}
})
},
async submitContribution(amount: number) {
console.log('submitContribution', amount)
const actions = get().actions
await actions.fetchWalletTokenAccounts()
const {
program,
provider,
pool,
tokenAccounts,
mints,
usdcVault,
current: wallet,
connection: { current: connection },
} = get()
const usdcDecimals = mints[usdcVault.mint.toBase58()].decimals
const redeemable = findLargestBalanceAccountForMint(
mints,
tokenAccounts,
pool.redeemableMint
)
const usdc = findLargestBalanceAccountForMint(
mints,
tokenAccounts,
usdcVault.mint
)
const difference = amount - (redeemable?.balance || 0)
const [poolSigner] = await anchor.web3.PublicKey.findProgramAddress(
[pool.watermelonMint.toBuffer()],
program.programId
)
if (difference > 0) {
const depositAmount = new anchor.BN(
difference * Math.pow(10, usdcDecimals)
)
console.log(depositAmount.toString(), 'exchangeUsdcForReemable')
let redeemableAccPk = redeemable?.account?.publicKey
const transaction = new Transaction()
if (!redeemable) {
const [ins, pk] = await createAssociatedTokenAccount(
wallet.publicKey,
wallet.publicKey,
pool.redeemableMint
)
transaction.add(ins)
redeemableAccPk = pk
}
transaction.add(
program.instruction.exchangeUsdcForRedeemable(depositAmount, {
accounts: {
poolAccount: POOL_PK,
poolSigner: poolSigner,
redeemableMint: pool.redeemableMint,
poolUsdc: pool.poolUsdc,
userAuthority: provider.wallet.publicKey,
userUsdc: usdc.account.publicKey,
userRedeemable: redeemableAccPk,
tokenProgram: TOKEN_PROGRAM_ID,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
})
)
await sendTransaction({ transaction, wallet, connection })
} else if (difference < 0) {
const withdrawAmount = new anchor.BN(
difference * -1 * Math.pow(10, usdcDecimals)
)
console.log(withdrawAmount.toString(), 'exchangeRedeemableForUsdc')
await program.rpc.exchangeRedeemableForUsdc(withdrawAmount, {
accounts: {
poolAccount: POOL_PK,
poolSigner: poolSigner,
redeemableMint: pool.redeemableMint,
poolUsdc: pool.poolUsdc,
userAuthority: provider.wallet.publicKey,
userUsdc: usdc.account.publicKey,
userRedeemable: redeemable.account.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
})
} else {
console.log('difference = 0 no submission needed', difference)
return
}
await actions.fetchWalletTokenAccounts()
actions.fetchUsdcVault()
},
async redeem() {
const actions = get().actions
await actions.fetchWalletTokenAccounts()
const {
program,
pool,
tokenAccounts,
mints,
current: wallet,
connection: { current: connection },
} = get()
const redeemable = findLargestBalanceAccountForMint(
mints,
tokenAccounts,
pool.redeemableMint
)
const watermelon = findLargestBalanceAccountForMint(
mints,
tokenAccounts,
pool.watermelonMint
)
console.log('exchangeRedeemableForMango', redeemable, watermelon)
const [poolSigner] = await anchor.web3.PublicKey.findProgramAddress(
[pool.watermelonMint.toBuffer()],
program.programId
)
const transaction = new Transaction()
let watermelonAccount = watermelon?.account?.publicKey
if (!watermelonAccount) {
const [ins, pk] = await createAssociatedTokenAccount(
wallet.publicKey,
wallet.publicKey,
pool.watermelonMint
)
transaction.add(ins)
watermelonAccount = pk
}
transaction.add(
program.instruction.exchangeRedeemableForWatermelon(
redeemable.account.account.amount,
{
accounts: {
poolAccount: POOL_PK,
poolSigner,
redeemableMint: pool.redeemableMint,
poolWatermelon: pool.poolWatermelon,
userAuthority: wallet.publicKey,
userWatermelon: watermelonAccount,
userRedeemable: redeemable.account.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
}
)
)
await sendTransaction({ transaction, wallet, connection })
await actions.fetchPool()
},
},
set: (fn) => set(produce(fn)),
}))
export default useWalletStore