connect to devnet contract
This commit is contained in:
parent
1d58b37d24
commit
816e82b867
|
@ -4,13 +4,13 @@ import tw from 'twin.macro'
|
||||||
import { LockClosedIcon, LockOpenIcon } from '@heroicons/react/outline'
|
import { LockClosedIcon, LockOpenIcon } from '@heroicons/react/outline'
|
||||||
import { LinkIcon } from '@heroicons/react/solid'
|
import { LinkIcon } from '@heroicons/react/solid'
|
||||||
import useWalletStore from '../stores/useWalletStore'
|
import useWalletStore from '../stores/useWalletStore'
|
||||||
import { getUsdcBalance } from '../utils'
|
|
||||||
import Input from './Input'
|
import Input from './Input'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import { ConnectWalletButtonSmall } from './ConnectWalletButton'
|
import { ConnectWalletButtonSmall } from './ConnectWalletButton'
|
||||||
import Slider from './Slider'
|
import Slider from './Slider'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import WalletIcon from './WalletIcon'
|
import WalletIcon from './WalletIcon'
|
||||||
|
import useLargestAccounts from '../hooks/useLargestAccounts'
|
||||||
|
|
||||||
const StyledModalWrapper = styled.div`
|
const StyledModalWrapper = styled.div`
|
||||||
height: 414px;
|
height: 414px;
|
||||||
|
@ -40,9 +40,15 @@ const StyledModalBorder = styled.div<StyledModalBorderProps>`
|
||||||
`
|
`
|
||||||
|
|
||||||
const ContributionModal = () => {
|
const ContributionModal = () => {
|
||||||
|
const actions = useWalletStore((s) => s.actions)
|
||||||
const connected = useWalletStore((s) => s.connected)
|
const connected = useWalletStore((s) => s.connected)
|
||||||
const wallet = useWalletStore((s) => s.current)
|
const wallet = useWalletStore((s) => s.current)
|
||||||
const usdcBalance = getUsdcBalance()
|
const largestAccounts = useLargestAccounts()
|
||||||
|
|
||||||
|
const usdcBalance = largestAccounts.usdc?.balance || 0
|
||||||
|
const redeemableBalance = largestAccounts.redeemable?.balance || 0
|
||||||
|
|
||||||
|
console.log({ usdcBalance, redeemableBalance })
|
||||||
|
|
||||||
const [contributionAmount, setContributionAmount] = useState(0)
|
const [contributionAmount, setContributionAmount] = useState(0)
|
||||||
const [sliderPercentage, setSliderPercentage] = useState(0)
|
const [sliderPercentage, setSliderPercentage] = useState(0)
|
||||||
|
@ -52,6 +58,11 @@ const ContributionModal = () => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [maxButtonTransition, setMaxButtonTransition] = useState(false)
|
const [maxButtonTransition, setMaxButtonTransition] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('setContributionAmount from redeemableBalance')
|
||||||
|
setContributionAmount(redeemableBalance)
|
||||||
|
}, [redeemableBalance])
|
||||||
|
|
||||||
const handleConnectDisconnect = () => {
|
const handleConnectDisconnect = () => {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
setSubmitted(false)
|
setSubmitted(false)
|
||||||
|
@ -103,12 +114,9 @@ const ContributionModal = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (submitting) {
|
if (submitting) {
|
||||||
// TODO: add submission here
|
actions.submitContribution(contributionAmount)
|
||||||
const submitTimer = setTimeout(() => {
|
setSubmitted(true)
|
||||||
setSubmitted(true)
|
setSubmitting(false)
|
||||||
setSubmitting(false)
|
|
||||||
}, 2000)
|
|
||||||
return () => clearTimeout(submitTimer)
|
|
||||||
}
|
}
|
||||||
}, [submitting])
|
}, [submitting])
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import BN from 'bn.js'
|
||||||
|
import useWalletStore from '../stores/useWalletStore'
|
||||||
|
import { ProgramAccount, TokenAccount } from '../utils/tokens'
|
||||||
|
|
||||||
|
function fixedPointToNumber(value: BN, decimals: number) {
|
||||||
|
const divisor = new BN(10).pow(new BN(decimals))
|
||||||
|
const quotient = value.div(divisor)
|
||||||
|
const remainder = value.mod(divisor)
|
||||||
|
return quotient.toNumber() + remainder.toNumber() / divisor.toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateBalance(mints, account: TokenAccount): number {
|
||||||
|
const mint = mints[account.mint.toBase58()]
|
||||||
|
return mint ? fixedPointToNumber(account.amount, mint.decimals) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findLargestBalanceAccountForMint(
|
||||||
|
mints,
|
||||||
|
tokenAccounts: ProgramAccount<TokenAccount>[],
|
||||||
|
mintPk
|
||||||
|
) {
|
||||||
|
const accounts = tokenAccounts.filter((a) => a.account.mint.equals(mintPk))
|
||||||
|
if (!accounts.length) return undefined
|
||||||
|
|
||||||
|
const balances = accounts.map((a) => calculateBalance(mints, a.account))
|
||||||
|
const maxBalanceAccountIndex = balances.reduce(
|
||||||
|
(iMax, bal, iBal) => (bal > balances[iMax] ? iBal : iMax),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const account = accounts[maxBalanceAccountIndex]
|
||||||
|
const balance = balances[maxBalanceAccountIndex]
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'findLargestBalanceAccountForMint',
|
||||||
|
maxBalanceAccountIndex,
|
||||||
|
account,
|
||||||
|
balance
|
||||||
|
)
|
||||||
|
|
||||||
|
return { account, balance }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useLargestAccounts() {
|
||||||
|
const { pool, tokenAccounts, mints, usdcVault } = useWalletStore(
|
||||||
|
(state) => state
|
||||||
|
)
|
||||||
|
const usdc = usdcVault
|
||||||
|
? findLargestBalanceAccountForMint(mints, tokenAccounts, usdcVault.mint)
|
||||||
|
: undefined
|
||||||
|
const redeemable = pool
|
||||||
|
? findLargestBalanceAccountForMint(
|
||||||
|
mints,
|
||||||
|
tokenAccounts,
|
||||||
|
pool.redeemableMint
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
return { usdc, redeemable }
|
||||||
|
}
|
|
@ -103,10 +103,9 @@ export default function useWallet() {
|
||||||
wallet.publicKey.toString().substr(-5),
|
wallet.publicKey.toString().substr(-5),
|
||||||
})
|
})
|
||||||
await actions.fetchPool()
|
await actions.fetchPool()
|
||||||
await actions.fetchVault()
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
actions.fetchWalletTokenAccounts(),
|
actions.fetchWalletTokenAccounts(),
|
||||||
actions.fetchVaultMint(),
|
actions.fetchMints(),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
wallet.on('disconnect', () => {
|
wallet.on('disconnect', () => {
|
||||||
|
@ -131,7 +130,7 @@ export default function useWallet() {
|
||||||
}, [wallet, setWalletStore])
|
}, [wallet, setWalletStore])
|
||||||
|
|
||||||
useInterval(async () => {
|
useInterval(async () => {
|
||||||
await actions.fetchVault()
|
await actions.fetchUsdcVault()
|
||||||
}, 20 * SECONDS)
|
}, 20 * SECONDS)
|
||||||
|
|
||||||
return { connected, wallet }
|
return { connected, wallet }
|
||||||
|
|
|
@ -397,6 +397,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"address": "5JTPKQJPkvrMzfDyEHX6EiaEYbR5QxXDkNQ66BoadgUn"
|
"address": "2oBtRS2AAQfsMxXQfg41fKFY9zjvHwSSD7G5idrCFziV"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import create, { State } from 'zustand'
|
import create, { State } from 'zustand'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { Connection, PublicKey } from '@solana/web3.js'
|
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
|
||||||
import * as anchor from '@project-serum/anchor'
|
import * as anchor from '@project-serum/anchor'
|
||||||
|
|
||||||
import { EndpointInfo, WalletAdapter } from '../@types/types'
|
import { EndpointInfo, WalletAdapter } from '../@types/types'
|
||||||
|
@ -16,6 +16,11 @@ import {
|
||||||
MintAccount,
|
MintAccount,
|
||||||
getTokenAccount,
|
getTokenAccount,
|
||||||
} from '../utils/tokens'
|
} from '../utils/tokens'
|
||||||
|
import { findLargestBalanceAccountForMint } from '../hooks/useLargestAccounts'
|
||||||
|
import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
|
||||||
|
import { token } from '@project-serum/anchor/dist/utils'
|
||||||
|
import { createAssociatedTokenAccount } from '../utils/associated'
|
||||||
|
import { sendTransaction } from '../utils/send'
|
||||||
|
|
||||||
export const ENDPOINTS: EndpointInfo[] = [
|
export const ENDPOINTS: EndpointInfo[] = [
|
||||||
{
|
{
|
||||||
|
@ -27,10 +32,10 @@ export const ENDPOINTS: EndpointInfo[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'devnet',
|
name: 'devnet',
|
||||||
url: 'https://devnet.solana.com',
|
url: 'https://api.devnet.solana.com',
|
||||||
websocket: 'https://devnet.solana.com',
|
websocket: 'https://api.devnet.solana.com',
|
||||||
programId: 'E5s3D6B3PJinuB9kb3dicxfi3qUNLUGX6hoPawhbqagt',
|
programId: '2oBtRS2AAQfsMxXQfg41fKFY9zjvHwSSD7G5idrCFziV',
|
||||||
poolKey: '',
|
poolKey: 'ZfSZf2xrNLBrfY37TwJLoHv9qdKBQpkbZuPrq5FT8U9',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'localnet',
|
name: 'localnet',
|
||||||
|
@ -41,7 +46,7 @@ export const ENDPOINTS: EndpointInfo[] = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const CLUSTER = 'localnet'
|
const CLUSTER = 'devnet'
|
||||||
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER)
|
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER)
|
||||||
const DEFAULT_CONNECTION = new Connection(ENDPOINT.url, 'recent')
|
const DEFAULT_CONNECTION = new Connection(ENDPOINT.url, 'recent')
|
||||||
const WEBSOCKET_CONNECTION = new Connection(ENDPOINT.websocket, 'recent')
|
const WEBSOCKET_CONNECTION = new Connection(ENDPOINT.websocket, 'recent')
|
||||||
|
@ -72,10 +77,13 @@ interface WalletStore extends State {
|
||||||
}
|
}
|
||||||
current: WalletAdapter | undefined
|
current: WalletAdapter | undefined
|
||||||
providerUrl: string
|
providerUrl: string
|
||||||
|
provider: anchor.Provider | undefined
|
||||||
|
program: anchor.Program | undefined
|
||||||
|
pool: PoolAccount | undefined
|
||||||
|
mangoVault: TokenAccount | undefined
|
||||||
|
usdcVault: TokenAccount | undefined
|
||||||
tokenAccounts: ProgramAccount<TokenAccount>[]
|
tokenAccounts: ProgramAccount<TokenAccount>[]
|
||||||
mints: { [pubkey: string]: MintAccount }
|
mints: { [pubkey: string]: MintAccount }
|
||||||
pool: PoolAccount | undefined
|
|
||||||
vault: TokenAccount | undefined
|
|
||||||
set: (x: any) => void
|
set: (x: any) => void
|
||||||
actions: any
|
actions: any
|
||||||
}
|
}
|
||||||
|
@ -91,10 +99,13 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
||||||
},
|
},
|
||||||
current: null,
|
current: null,
|
||||||
providerUrl: null,
|
providerUrl: null,
|
||||||
|
provider: undefined,
|
||||||
|
program: undefined,
|
||||||
|
pool: undefined,
|
||||||
|
mangoVault: undefined,
|
||||||
|
usdcVault: undefined,
|
||||||
tokenAccounts: [],
|
tokenAccounts: [],
|
||||||
mints: {},
|
mints: {},
|
||||||
pool: undefined,
|
|
||||||
vault: undefined,
|
|
||||||
actions: {
|
actions: {
|
||||||
async fetchPool() {
|
async fetchPool() {
|
||||||
const connection = get().connection.current
|
const connection = get().connection.current
|
||||||
|
@ -110,15 +121,23 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
||||||
anchor.Provider.defaultOptions()
|
anchor.Provider.defaultOptions()
|
||||||
)
|
)
|
||||||
const program = new anchor.Program(poolIdl, programId, provider)
|
const program = new anchor.Program(poolIdl, programId, provider)
|
||||||
console.log(program)
|
|
||||||
|
|
||||||
const pool = (await program.account.poolAccount.fetch(
|
const pool = (await program.account.poolAccount.fetch(
|
||||||
POOL_PK
|
POOL_PK
|
||||||
)) as PoolAccount
|
)) as PoolAccount
|
||||||
console.log(pool)
|
|
||||||
|
const [usdcVault, mangoVault] = await Promise.all([
|
||||||
|
getTokenAccount(connection, pool.poolUsdc),
|
||||||
|
getTokenAccount(connection, pool.poolWatermelon),
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log({ program, pool, usdcVault, mangoVault })
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
|
state.provider = provider
|
||||||
|
state.program = program
|
||||||
state.pool = pool
|
state.pool = pool
|
||||||
|
state.usdcVault = usdcVault.account
|
||||||
|
state.mangoVault = mangoVault.account
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -135,6 +154,8 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
||||||
walletOwner
|
walletOwner
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log('fetchWalletTokenAccounts', ownedTokenAccounts)
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.tokenAccounts = ownedTokenAccounts
|
state.tokenAccounts = ownedTokenAccounts
|
||||||
})
|
})
|
||||||
|
@ -144,7 +165,7 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchVault() {
|
async fetchUsdcVault() {
|
||||||
const connection = get().connection.current
|
const connection = get().connection.current
|
||||||
const pool = get().pool
|
const pool = get().pool
|
||||||
const set = get().set
|
const set = get().set
|
||||||
|
@ -155,24 +176,124 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
||||||
connection,
|
connection,
|
||||||
pool.poolUsdc
|
pool.poolUsdc
|
||||||
)
|
)
|
||||||
console.log('fetchVault', vault)
|
console.log('fetchUsdcVault', vault)
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.vault = vault
|
state.usdcVault = vault
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async fetchVaultMint() {
|
async fetchMints() {
|
||||||
const connection = get().connection.current
|
const connection = get().connection.current
|
||||||
const vault = get().vault
|
const pool = get().pool
|
||||||
|
const mangoVault = get().mangoVault
|
||||||
|
const usdcVault = get().usdcVault
|
||||||
const set = get().set
|
const set = get().set
|
||||||
|
|
||||||
const { account: mint } = await getMint(connection, vault.mint)
|
const mintKeys = [mangoVault.mint, usdcVault.mint, pool.redeemableMint]
|
||||||
console.log('fetchVaultMint', mint)
|
const mints = await Promise.all(
|
||||||
|
mintKeys.map((pk) => getMint(connection, pk))
|
||||||
|
)
|
||||||
|
console.log('fetchMints', mints)
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.mints[vault.mint.toBase58()] = mint
|
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()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
set: (fn) => set(produce(fn)),
|
set: (fn) => set(produce(fn)),
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
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, PublicKey]> {
|
||||||
|
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([]),
|
||||||
|
}),
|
||||||
|
associatedTokenAddress,
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
import BN from 'bn.js'
|
|
||||||
import useWalletStore from '../stores/useWalletStore'
|
|
||||||
|
|
||||||
function fixedPointToNumber(value: BN, decimals: number) {
|
|
||||||
const divisor = new BN(10).pow(new BN(decimals))
|
|
||||||
const quotient = value.div(divisor)
|
|
||||||
const remainder = value.mod(divisor)
|
|
||||||
return quotient.toNumber() + remainder.toNumber() / divisor.toNumber()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUsdcBalance() {
|
|
||||||
const { tokenAccounts, mints, vault } = useWalletStore((state) => state)
|
|
||||||
|
|
||||||
if (!vault) return 0
|
|
||||||
|
|
||||||
const calculateBalance = (a) => {
|
|
||||||
const mint = mints[a.account?.mint?.toBase58()]
|
|
||||||
return mint ? fixedPointToNumber(a.account.amount, mint.decimals) : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const usdcAddress = vault.mint.toBase58()
|
|
||||||
|
|
||||||
const usdcAccount = tokenAccounts.filter(
|
|
||||||
(a) => a.account.mint.toBase58() === usdcAddress
|
|
||||||
)
|
|
||||||
|
|
||||||
const usdcBalance = usdcAccount.map((a) => calculateBalance(a))
|
|
||||||
|
|
||||||
return usdcBalance.length ? usdcBalance[0] : 0
|
|
||||||
}
|
|
|
@ -0,0 +1,292 @@
|
||||||
|
import { notify } from './notifications'
|
||||||
|
import {
|
||||||
|
Account,
|
||||||
|
AccountInfo,
|
||||||
|
Commitment,
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
RpcResponseAndContext,
|
||||||
|
SimulatedTransactionResponse,
|
||||||
|
Transaction,
|
||||||
|
TransactionSignature,
|
||||||
|
} from '@solana/web3.js'
|
||||||
|
import Wallet from '@project-serum/sol-wallet-adapter'
|
||||||
|
import { Buffer } from 'buffer'
|
||||||
|
import assert from 'assert'
|
||||||
|
import { struct } from 'superstruct'
|
||||||
|
|
||||||
|
class TransactionError extends Error {
|
||||||
|
public txid: string
|
||||||
|
constructor(message: string, txid?: string) {
|
||||||
|
super(message)
|
||||||
|
this.txid = txid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUnixTs() {
|
||||||
|
return new Date().getTime() / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TIMEOUT = 30000
|
||||||
|
|
||||||
|
export async function sendTransaction({
|
||||||
|
transaction,
|
||||||
|
wallet,
|
||||||
|
signers = [],
|
||||||
|
connection,
|
||||||
|
sendingMessage = 'Sending transaction...',
|
||||||
|
successMessage = 'Transaction confirmed',
|
||||||
|
timeout = DEFAULT_TIMEOUT,
|
||||||
|
}: {
|
||||||
|
transaction: Transaction
|
||||||
|
wallet: Wallet
|
||||||
|
signers?: Array<Account>
|
||||||
|
connection: Connection
|
||||||
|
sendingMessage?: string
|
||||||
|
successMessage?: string
|
||||||
|
timeout?: number
|
||||||
|
}) {
|
||||||
|
const signedTransaction = await signTransaction({
|
||||||
|
transaction,
|
||||||
|
wallet,
|
||||||
|
signers,
|
||||||
|
connection,
|
||||||
|
})
|
||||||
|
return await sendSignedTransaction({
|
||||||
|
signedTransaction,
|
||||||
|
connection,
|
||||||
|
sendingMessage,
|
||||||
|
successMessage,
|
||||||
|
timeout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signTransaction({
|
||||||
|
transaction,
|
||||||
|
wallet,
|
||||||
|
signers = [],
|
||||||
|
connection,
|
||||||
|
}: {
|
||||||
|
transaction: Transaction
|
||||||
|
wallet: Wallet
|
||||||
|
signers?: Array<Account>
|
||||||
|
connection: Connection
|
||||||
|
}) {
|
||||||
|
transaction.recentBlockhash = (
|
||||||
|
await connection.getRecentBlockhash('max')
|
||||||
|
).blockhash
|
||||||
|
transaction.setSigners(wallet.publicKey, ...signers.map((s) => s.publicKey))
|
||||||
|
if (signers.length > 0) {
|
||||||
|
transaction.partialSign(...signers)
|
||||||
|
}
|
||||||
|
return await wallet.signTransaction(transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signTransactions({
|
||||||
|
transactionsAndSigners,
|
||||||
|
wallet,
|
||||||
|
connection,
|
||||||
|
}: {
|
||||||
|
transactionsAndSigners: {
|
||||||
|
transaction: Transaction
|
||||||
|
signers?: Array<Account>
|
||||||
|
}[]
|
||||||
|
wallet: Wallet
|
||||||
|
connection: Connection
|
||||||
|
}) {
|
||||||
|
const blockhash = (await connection.getRecentBlockhash('max')).blockhash
|
||||||
|
transactionsAndSigners.forEach(({ transaction, signers = [] }) => {
|
||||||
|
transaction.recentBlockhash = blockhash
|
||||||
|
transaction.setSigners(wallet.publicKey, ...signers.map((s) => s.publicKey))
|
||||||
|
if (signers?.length > 0) {
|
||||||
|
transaction.partialSign(...signers)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return await wallet.signAllTransactions(
|
||||||
|
transactionsAndSigners.map(({ transaction }) => transaction)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendSignedTransaction({
|
||||||
|
signedTransaction,
|
||||||
|
connection,
|
||||||
|
sendingMessage = 'Sending transaction...',
|
||||||
|
successMessage = 'Transaction confirmed',
|
||||||
|
timeout = DEFAULT_TIMEOUT,
|
||||||
|
}: {
|
||||||
|
signedTransaction: Transaction
|
||||||
|
connection: Connection
|
||||||
|
sendingMessage?: string
|
||||||
|
successMessage?: string
|
||||||
|
timeout?: number
|
||||||
|
}): Promise<string> {
|
||||||
|
const rawTransaction = signedTransaction.serialize()
|
||||||
|
const startTime = getUnixTs()
|
||||||
|
notify({ message: sendingMessage })
|
||||||
|
const txid: TransactionSignature = await connection.sendRawTransaction(
|
||||||
|
rawTransaction,
|
||||||
|
{
|
||||||
|
skipPreflight: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('Started awaiting confirmation for', txid)
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
;(async () => {
|
||||||
|
while (!done && getUnixTs() - startTime < timeout) {
|
||||||
|
connection.sendRawTransaction(rawTransaction, {
|
||||||
|
skipPreflight: true,
|
||||||
|
})
|
||||||
|
await sleep(300)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
try {
|
||||||
|
await awaitTransactionSignatureConfirmation(txid, timeout, connection)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.timeout) {
|
||||||
|
throw new Error('Timed out awaiting confirmation on transaction')
|
||||||
|
}
|
||||||
|
let simulateResult: SimulatedTransactionResponse | null = null
|
||||||
|
try {
|
||||||
|
simulateResult = (
|
||||||
|
await simulateTransaction(connection, signedTransaction, 'single')
|
||||||
|
).value
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error: ', e)
|
||||||
|
}
|
||||||
|
if (simulateResult && simulateResult.err) {
|
||||||
|
if (simulateResult.logs) {
|
||||||
|
for (let i = simulateResult.logs.length - 1; i >= 0; --i) {
|
||||||
|
const line = simulateResult.logs[i]
|
||||||
|
if (line.startsWith('Program log: ')) {
|
||||||
|
throw new TransactionError(
|
||||||
|
'Transaction failed: ' + line.slice('Program log: '.length),
|
||||||
|
txid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new TransactionError(JSON.stringify(simulateResult.err), txid)
|
||||||
|
}
|
||||||
|
throw new TransactionError('Transaction failed', txid)
|
||||||
|
} finally {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
notify({ message: successMessage, type: 'success', txid })
|
||||||
|
|
||||||
|
console.log('Latency', txid, getUnixTs() - startTime)
|
||||||
|
return txid
|
||||||
|
}
|
||||||
|
|
||||||
|
async function awaitTransactionSignatureConfirmation(
|
||||||
|
txid: TransactionSignature,
|
||||||
|
timeout: number,
|
||||||
|
connection: Connection
|
||||||
|
) {
|
||||||
|
let done = false
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
;(async () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (done) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
console.log('Timed out for txid', txid)
|
||||||
|
reject({ timeout: true })
|
||||||
|
}, timeout)
|
||||||
|
try {
|
||||||
|
connection.onSignature(
|
||||||
|
txid,
|
||||||
|
(result) => {
|
||||||
|
console.log('WS confirmed', txid, result)
|
||||||
|
done = true
|
||||||
|
if (result.err) {
|
||||||
|
reject(result.err)
|
||||||
|
} else {
|
||||||
|
resolve(result)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
connection.commitment
|
||||||
|
)
|
||||||
|
console.log('Set up WS connection', txid)
|
||||||
|
} catch (e) {
|
||||||
|
done = true
|
||||||
|
console.log('WS error in setup', txid, e)
|
||||||
|
}
|
||||||
|
while (!done) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
;(async () => {
|
||||||
|
try {
|
||||||
|
const signatureStatuses = await connection.getSignatureStatuses([
|
||||||
|
txid,
|
||||||
|
])
|
||||||
|
const result = signatureStatuses && signatureStatuses.value[0]
|
||||||
|
if (!done) {
|
||||||
|
if (!result) {
|
||||||
|
// console.log('REST null result for', txid, result);
|
||||||
|
} else if (result.err) {
|
||||||
|
console.log('REST error for', txid, result)
|
||||||
|
done = true
|
||||||
|
reject(result.err)
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
else if (
|
||||||
|
!(
|
||||||
|
result.confirmations ||
|
||||||
|
result.confirmationStatus === 'confirmed' ||
|
||||||
|
result.confirmationStatus === 'finalized'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.log('REST not confirmed', txid, result)
|
||||||
|
} else {
|
||||||
|
console.log('REST confirmed', txid, result)
|
||||||
|
done = true
|
||||||
|
resolve(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!done) {
|
||||||
|
console.log('REST connection error: txid', txid, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
await sleep(300)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
done = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copy of Connection.simulateTransaction that takes a commitment parameter. */
|
||||||
|
async function simulateTransaction(
|
||||||
|
connection: Connection,
|
||||||
|
transaction: Transaction,
|
||||||
|
commitment: Commitment
|
||||||
|
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
|
||||||
|
// @ts-ignore
|
||||||
|
transaction.recentBlockhash = await connection._recentBlockhash(
|
||||||
|
// @ts-ignore
|
||||||
|
connection._disableBlockhashCaching
|
||||||
|
)
|
||||||
|
|
||||||
|
const signData = transaction.serializeMessage()
|
||||||
|
// @ts-ignore
|
||||||
|
const wireTransaction = transaction._serialize(signData)
|
||||||
|
const encodedTransaction = wireTransaction.toString('base64')
|
||||||
|
const config: any = { encoding: 'base64', commitment }
|
||||||
|
const args = [encodedTransaction, config]
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const res = await connection._rpcRequest('simulateTransaction', args)
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error('failed to simulate transaction: ' + res.error.message)
|
||||||
|
}
|
||||||
|
return res.result
|
||||||
|
}
|
Loading…
Reference in New Issue