support nfts as profile pics

This commit is contained in:
saml33 2022-05-11 13:29:35 +10:00
parent a3fc5b2a57
commit 2aeff1032e
21 changed files with 2837 additions and 87 deletions

View File

@ -12,15 +12,17 @@ import {
CurrencyDollarIcon,
DuplicateIcon,
LogoutIcon,
UserCircleIcon,
} from '@heroicons/react/outline'
import { notify } from 'utils/notifications'
import { abbreviateAddress, copyToClipboard } from 'utils'
import useMangoStore from 'stores/useMangoStore'
import { ProfileIcon, WalletIcon } from './icons'
import { WalletIcon, ProfileIcon } from './icons'
import { useTranslation } from 'next-i18next'
import { WalletSelect } from 'components/WalletSelect'
import AccountsModal from './AccountsModal'
import uniqBy from 'lodash/uniqBy'
import NftProfilePicModal from './NftProfilePicModal'
export const handleWalletConnect = (wallet: Wallet) => {
if (!wallet) {
@ -43,11 +45,15 @@ export const handleWalletConnect = (wallet: Wallet) => {
export const ConnectWalletButton: React.FC = () => {
const { connected, publicKey, wallet, wallets, select } = useWallet()
const { t } = useTranslation('common')
const { t } = useTranslation(['common', 'profile'])
const pfp = useMangoStore((s) => s.wallet.pfp)
const loadingTransaction = useMangoStore(
(s) => s.wallet.nfts.loadingTransaction
)
const set = useMangoStore((s) => s.set)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const [showAccountsModal, setShowAccountsModal] = useState(false)
const [showProfilePicModal, setShowProfilePicModal] = useState(false)
const installedWallets = useMemo(() => {
const installed: Wallet[] = []
@ -77,6 +83,10 @@ export const ConnectWalletButton: React.FC = () => {
setShowAccountsModal(false)
}, [])
const handleCloseProfilePicModal = useCallback(() => {
setShowProfilePicModal(false)
}, [])
const handleDisconnect = useCallback(() => {
wallet?.adapter?.disconnect()
set((state) => {
@ -107,11 +117,21 @@ export const ConnectWalletButton: React.FC = () => {
<Menu>
{({ open }) => (
<div className="relative" id="profile-menu-tip">
<Menu.Button className="flex h-10 w-10 items-center justify-center rounded-full bg-th-bkg-4 text-white hover:bg-th-bkg-4 hover:text-th-fgd-3 focus:outline-none">
<Menu.Button
className={`flex h-10 w-10 items-center justify-center rounded-full bg-th-bkg-button hover:bg-th-bkg-4 hover:bg-th-bkg-4 hover:text-th-fgd-3 focus:outline-none ${
loadingTransaction ? 'animate-pulse bg-th-bkg-4' : ''
}`}
>
{pfp?.isAvailable ? (
<img alt="" src={pfp.url} className="rounded-full" />
<img
alt=""
src={pfp.url}
className={`default-transition h-10 w-10 rounded-full hover:opacity-60 ${
loadingTransaction ? 'opacity-40' : ''
}`}
/>
) : (
<ProfileIcon className="h-6 w-6" />
<ProfileIcon className="h-6 w-6 text-th-fgd-3" />
)}
</Menu.Button>
<Transition
@ -135,6 +155,19 @@ export const ConnectWalletButton: React.FC = () => {
<div className="pl-2 text-left">{t('accounts')}</div>
</button>
</Menu.Item>
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
onClick={() => setShowProfilePicModal(true)}
>
<UserCircleIcon className="h-4 w-4" />
<div className="pl-2 text-left">
{pfp?.isAvailable
? t('profile:edit-profile-pic')
: t('profile:set-profile-pic')}
</div>
</button>
</Menu.Item>
<Menu.Item>
<button
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
@ -198,6 +231,12 @@ export const ConnectWalletButton: React.FC = () => {
isOpen={showAccountsModal}
/>
)}
{showProfilePicModal && (
<NftProfilePicModal
onClose={handleCloseProfilePicModal}
isOpen={showProfilePicModal}
/>
)}
</>
)
}

View File

@ -0,0 +1,260 @@
import { useState, useEffect } from 'react'
import { PublicKey } from '@solana/web3.js'
import { notify } from 'utils/notifications'
import useMangoStore from '../stores/useMangoStore'
import { useWallet } from '@solana/wallet-adapter-react'
import { PhotographIcon } from '@heroicons/react/outline'
import Modal from './Modal'
import { ElementTitle } from './styles'
import {
createRemoveProfilePictureTransaction,
createSetProfilePictureTransaction,
} from '@solflare-wallet/pfp'
import Button from './Button'
import { useTranslation } from 'next-i18next'
import { connectionSelector } from '../stores/selectors'
import { LinkButton } from 'components'
interface SelectedNft {
mint: PublicKey
tokenAddress: PublicKey
}
const ImgWithLoader = (props) => {
const [isLoading, setIsLoading] = useState(true)
return (
<div className="relative">
{isLoading && (
<PhotographIcon className="absolute left-1/2 top-1/2 z-10 h-1/4 w-1/4 -translate-x-1/2 -translate-y-1/2 transform animate-pulse text-th-fgd-4" />
)}
<img {...props} onLoad={() => setIsLoading(false)} />
</div>
)
}
const NftProfilePicModal = ({ isOpen, onClose }) => {
const { t } = useTranslation(['common', 'profile'])
const connection = useMangoStore(connectionSelector)
const { publicKey, wallet } = useWallet()
const pfp = useMangoStore((s) => s.wallet.pfp)
const nfts = useMangoStore((s) => s.wallet.nfts.data)
const nftAccounts = useMangoStore((s) => s.wallet.nfts.accounts)
const initialLoad = useMangoStore((s) => s.wallet.nfts.initialLoad)
const nftsLoading = useMangoStore((s) => s.wallet.nfts.loading)
const [selectedProfile, setSelectedProfile] = useState<SelectedNft | null>(
null
)
const actions = useMangoStore((s) => s.actions)
const mangoClient = useMangoStore.getState().connection.client
const [offset, setOffset] = useState(0)
const setMangoStore = useMangoStore((s) => s.set)
useEffect(() => {
if (publicKey) {
actions.fetchNftAccounts(connection, publicKey)
}
}, [publicKey])
useEffect(() => {
if (!initialLoad && publicKey && nftAccounts.length > 0) {
actions.fetchNfts(connection, publicKey)
}
}, [publicKey, initialLoad, nftAccounts])
useEffect(() => {
if (pfp?.isAvailable && pfp.mintAccount && pfp.tokenAccount) {
setSelectedProfile({
mint: pfp.mintAccount,
tokenAddress: pfp.tokenAccount,
})
}
}, [pfp])
const handleSaveProfilePic = async () => {
if (!publicKey || !selectedProfile || !wallet) {
return
}
try {
setMangoStore((state) => {
state.wallet.nfts.loadingTransaction = true
})
onClose()
const transaction = await createSetProfilePictureTransaction(
publicKey,
selectedProfile.mint,
selectedProfile.tokenAddress
)
if (transaction) {
const txid = await mangoClient.sendTransaction(
transaction,
wallet.adapter,
[]
)
if (txid) {
notify({
title: t('profile:profile-pic-success'),
description: '',
txid,
})
} else {
notify({
title: t('profile:profile-pic-failure'),
description: t('transaction-failed'),
})
}
} else {
notify({
title: t('profile:profile-pic-failure'),
description: t('transaction-failed'),
})
}
} catch (e) {
notify({
title: t('profile:profile-pic-failure'),
description: e.message,
txid: e.txid,
type: 'error',
})
} finally {
actions.fetchProfilePicture(wallet)
setMangoStore((state) => {
state.wallet.nfts.loadingTransaction = false
})
}
}
const handleRemoveProfilePic = async () => {
if (!publicKey || !wallet) {
return
}
try {
setMangoStore((state) => {
state.wallet.nfts.loadingTransaction = true
})
onClose()
const transaction = await createRemoveProfilePictureTransaction(publicKey)
if (transaction) {
const txid = await mangoClient.sendTransaction(
transaction,
wallet.adapter,
[]
)
if (txid) {
notify({
title: t('profile:profile-pic-remove-success'),
description: '',
txid,
})
} else {
notify({
title: t('profile:profile-pic-remove-failure'),
description: t('transaction-failed'),
})
}
} else {
notify({
title: t('profile:profile-pic-remove-failure'),
description: t('transaction-failed'),
})
}
} catch (e) {
notify({
title: t('profile:profile-pic-remove-failure'),
description: e.message,
txid: e.txid,
type: 'error',
})
} finally {
actions.fetchProfilePicture(wallet)
setMangoStore((state) => {
state.wallet.nfts.loadingTransaction = false
})
}
}
const handleLoadMore = async () => {
const offsetNfts = offset + 9
await actions.fetchNfts(connection, publicKey, offsetNfts)
setOffset(offsetNfts)
}
return (
<Modal isOpen={isOpen} onClose={onClose}>
<Modal.Header>
<div className="my-3 flex w-full items-center justify-between">
<ElementTitle noMarginBottom>
{t('profile:choose-profile')}
</ElementTitle>
<div className="flex items-center space-x-4">
<Button
disabled={!selectedProfile}
onClick={() => handleSaveProfilePic()}
>
{t('save')}
</Button>
{pfp?.isAvailable ? (
<LinkButton
className="text-xs"
onClick={() => handleRemoveProfilePic()}
>
{t('profile:remove')}
</LinkButton>
) : null}
</div>
</div>
</Modal.Header>
{nftAccounts.length > 0 ? (
<div className="flex flex-col items-center">
<div className="mb-4 grid w-full grid-flow-row grid-cols-3 gap-4">
{nfts.map((n) => {
return (
<button
className={`default-transitions col-span-1 flex items-center justify-center rounded-md border bg-th-bkg-3 py-4 hover:bg-th-bkg-4 ${
selectedProfile?.tokenAddress.toString() === n.tokenAddress
? 'border-th-primary'
: 'border-th-bkg-3'
}`}
key={n.tokenAddress}
onClick={() =>
setSelectedProfile({
mint: new PublicKey(n.mint),
tokenAddress: new PublicKey(n.tokenAddress),
})
}
>
<ImgWithLoader
className="h-20 w-20 flex-shrink-0 rounded-full"
src={n.val.image}
/>
</button>
)
})}
{nftsLoading
? [
...Array(
nftAccounts.length - nfts.length > 9
? 9
: nftAccounts.length - nfts.length
),
].map((i) => (
<div
className="col-span-1 h-28 animate-pulse rounded-md bg-th-bkg-3"
key={i}
/>
))
: null}
</div>
{nftAccounts.length !== nfts.length ? (
<LinkButton onClick={() => handleLoadMore()}>
{t('show-more')}
</LinkButton>
) : null}
</div>
) : null}
</Modal>
)
}
export default NftProfilePicModal

View File

@ -21,6 +21,7 @@
"@headlessui/react": "^0.0.0-insiders.2dbc38c",
"@heroicons/react": "^1.0.0",
"@jup-ag/react-hook": "^1.0.0-beta.22",
"@nfteyez/sol-rayz": "^0.10.2",
"@notifi-network/notifi-react-hooks": "^0.12.1",
"@project-serum/serum": "0.13.55",
"@project-serum/sol-wallet-adapter": "0.2.0",

View File

@ -54,6 +54,8 @@ import { Menu, Transition } from '@headlessui/react'
import { useWallet } from '@solana/wallet-adapter-react'
import { handleWalletConnect } from 'components/ConnectWalletButton'
import { MangoAccountLookup } from 'components/account_page/MangoAccountLookup'
import NftProfilePicModal from 'components/NftProfilePicModal'
import { ProfileIcon } from 'components'
export async function getStaticProps({ locale }) {
return {
@ -64,6 +66,7 @@ export async function getStaticProps({ locale }) {
'delegate',
'alerts',
'share-modal',
'profile',
])),
// Will be passed to the page component as props
},
@ -92,6 +95,11 @@ export default function Account() {
const [mngoAccrued, setMngoAccrued] = useState(ZERO_BN)
const [viewIndex, setViewIndex] = useState(0)
const [activeTab, setActiveTab] = useState(TABS[0])
const [showProfilePicModal, setShowProfilePicModal] = useState(false)
const pfp = useMangoStore((s) => s.wallet.pfp)
const loadingTransaction = useMangoStore(
(s) => s.wallet.nfts.loadingTransaction
)
const connecting = wallet?.adapter?.connecting
const isMobile = width ? width < breakpoints.sm : false
@ -120,6 +128,10 @@ export default function Account() {
setShowDelegateModal(false)
}, [])
const handleCloseProfilePicModal = useCallback(() => {
setShowProfilePicModal(false)
}, [])
const handleConnect = useCallback(() => {
if (wallet) {
handleWalletConnect(wallet)
@ -265,41 +277,67 @@ export default function Account() {
<div className="flex flex-col pt-4 pb-6 md:flex-row md:items-end md:justify-between md:pb-4 md:pt-10">
{mangoAccount ? (
<>
<div className="pb-3 md:pb-0">
<div className="mb-1 flex items-center">
<h1 className={`mr-3`}>
{mangoAccount?.name || t('account')}
</h1>
{!pubkey ? (
<IconButton
className="h-7 w-7"
onClick={() => setShowNameModal(true)}
<div className="flex items-center pb-3 md:pb-0">
<button
disabled={!!pubkey}
className={`relative mr-4 flex h-20 w-20 items-center justify-center rounded-full ${
loadingTransaction
? 'animate-pulse bg-th-bkg-4'
: 'bg-th-bkg-button'
}`}
onClick={() => setShowProfilePicModal(true)}
>
{pfp?.isAvailable ? (
<img
alt=""
src={pfp.url}
className={`default-transition h-20 w-20 rounded-full hover:opacity-60 ${
loadingTransaction ? 'opacity-40' : ''
}`}
/>
) : (
<ProfileIcon className="h-12 w-12 text-th-fgd-3" />
)}
<div className="default-transition absolute bottom-0 top-0 left-0 right-0 flex h-full w-full items-center justify-center rounded-full bg-[rgba(0,0,0,0.6)] opacity-0 hover:opacity-100">
<PencilIcon className="h-5 w-5 text-th-fgd-1" />
</div>
</button>
<div>
<div className="mb-1 flex items-center">
<h1 className={`mr-3`}>
{mangoAccount?.name || t('account')}
</h1>
{!pubkey ? (
<IconButton
className="h-7 w-7"
onClick={() => setShowNameModal(true)}
>
<PencilIcon className="h-3.5 w-3.5" />
</IconButton>
) : null}
</div>
<div className="flex h-4 items-center">
<LinkButton
className="flex items-center text-th-fgd-4 no-underline"
onClick={() =>
handleCopyAddress(mangoAccount.publicKey.toString())
}
>
<PencilIcon className="h-3.5 w-3.5" />
</IconButton>
) : null}
</div>
<div className="flex h-4 items-center">
<LinkButton
className="flex items-center text-th-fgd-4 no-underline"
onClick={() =>
handleCopyAddress(mangoAccount.publicKey.toString())
}
>
<span className="text-xxs font-normal sm:text-xs">
{mangoAccount.publicKey.toBase58()}
</span>
<DuplicateIcon className="ml-1.5 h-4 w-4" />
</LinkButton>
{isCopied ? (
<span className="ml-2 rounded bg-th-bkg-3 px-1.5 py-0.5 text-xs">
Copied
</span>
) : null}
</div>
<div className="flex items-center text-xxs text-th-fgd-4">
<ExclamationCircleIcon className="mr-1.5 h-4 w-4" />
{t('account-address-warning')}
<span className="text-xxs font-normal sm:text-xs">
{mangoAccount.publicKey.toBase58()}
</span>
<DuplicateIcon className="ml-1.5 h-4 w-4" />
</LinkButton>
{isCopied ? (
<span className="ml-2 rounded bg-th-bkg-3 px-1.5 py-0.5 text-xs">
Copied
</span>
) : null}
</div>
<div className="flex items-center text-xxs text-th-fgd-4">
<ExclamationCircleIcon className="mr-1.5 h-4 w-4" />
{t('account-address-warning')}
</div>
</div>
</div>
{!pubkey ? (
@ -502,6 +540,12 @@ export default function Account() {
onClose={handleCloseDelegateModal}
/>
) : null}
{showProfilePicModal ? (
<NftProfilePicModal
isOpen={showProfilePicModal}
onClose={handleCloseProfilePicModal}
/>
) : null}
</div>
)
}

View File

@ -11,7 +11,7 @@ import { useTranslation } from 'next-i18next'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
...(await serverSideTranslations(locale, ['common', 'profile'])),
// Will be passed to the page component as props
},
}

View File

@ -22,7 +22,7 @@ import { useWallet } from '@solana/wallet-adapter-react'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
...(await serverSideTranslations(locale, ['common', 'profile'])),
// Will be passed to the page component as props
},
}

View File

@ -31,6 +31,7 @@ export async function getStaticProps({ locale }) {
'tv-chart',
'alerts',
'share-modal',
'profile',
])),
// Will be passed to the page component as props
},

View File

@ -11,7 +11,7 @@ const TABS = ['perp', 'spot']
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
...(await serverSideTranslations(locale, ['common', 'profile'])),
// Will be passed to the page component as props
},
}

View File

@ -52,7 +52,11 @@ import { useWallet } from '@solana/wallet-adapter-react'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'referrals'])),
...(await serverSideTranslations(locale, [
'common',
'referrals',
'profile',
])),
// Will be passed to the page component as props
},
}

View File

@ -34,7 +34,11 @@ import { useWallet } from '@solana/wallet-adapter-react'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'calculator'])),
...(await serverSideTranslations(locale, [
'common',
'calculator',
'profile',
])),
},
}
}

View File

@ -11,7 +11,11 @@ import TopBar from '../components/TopBar'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'tv-chart'])),
...(await serverSideTranslations(locale, [
'common',
'tv-chart',
'profile',
])),
// Will be passed to the page component as props
},
}

View File

@ -16,7 +16,7 @@ import { useTranslation } from 'next-i18next'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
...(await serverSideTranslations(locale, ['common', 'profile'])),
// Will be passed to the page component as props
},
}

View File

@ -13,7 +13,7 @@ import { useWallet } from '@solana/wallet-adapter-react'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ['common', 'swap'])),
...(await serverSideTranslations(locale, ['common', 'swap', 'profile'])),
// Will be passed to the page component as props
},
}

View File

@ -83,7 +83,7 @@
"connect-wallet-tip-desc": "We'll show you around...",
"connect-wallet-tip-title": "Connect your wallet",
"connected-to": "Connected to wallet ",
"copy-address": "Copy address",
"copy-address": "Copy Address",
"country-not-allowed": "Country Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
@ -356,7 +356,7 @@
"short": "short",
"show-all": "Show all in Nav",
"show-less": "Show less",
"show-more": "Show more",
"show-more": "Show More",
"show-tips": "Show Tips",
"show-zero": "Show zero balances",
"side": "Side",

View File

@ -0,0 +1,10 @@
{
"choose-profile": "Choose a Profile Pic",
"edit-profile-pic": "Edit Profile Pic",
"profile-pic-failure": "Failed to set profile pic",
"profile-pic-success": "Successfully set profile pic",
"profile-pic-remove-failure": "Failed to remove profile pic",
"profile-pic-remove-success": "Successfully removed profile pic",
"remove": "Remove",
"set-profile-pic": "Set Profile Pic"
}

View File

@ -0,0 +1,10 @@
{
"choose-profile": "Choose a Profile Pic",
"edit-profile-pic": "Edit Profile Pic",
"profile-pic-failure": "Failed to set profile pic",
"profile-pic-success": "Successfully set profile pic",
"profile-pic-remove-failure": "Failed to remove profile pic",
"profile-pic-remove-success": "Successfully removed profile pic",
"remove": "Remove",
"set-profile-pic": "Set Profile Pic"
}

View File

@ -0,0 +1,10 @@
{
"choose-profile": "Choose a Profile Pic",
"edit-profile-pic": "Edit Profile Pic",
"profile-pic-failure": "Failed to set profile pic",
"profile-pic-success": "Successfully set profile pic",
"profile-pic-remove-failure": "Failed to remove profile pic",
"profile-pic-remove-success": "Successfully removed profile pic",
"remove": "Remove",
"set-profile-pic": "Set Profile Pic"
}

View File

@ -0,0 +1,10 @@
{
"choose-profile": "Choose a Profile Pic",
"edit-profile-pic": "Edit Profile Pic",
"profile-pic-failure": "Failed to set profile pic",
"profile-pic-success": "Successfully set profile pic",
"profile-pic-remove-failure": "Failed to remove profile pic",
"profile-pic-remove-success": "Successfully removed profile pic",
"remove": "Remove",
"set-profile-pic": "Set Profile Pic"
}

View File

@ -40,6 +40,8 @@ import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp'
import { decodeBook } from '../hooks/useHydrateStore'
import { IOrderLineAdapter } from '../public/charting_library/charting_library'
import { Wallet } from '@solana/wallet-adapter-react'
import { getParsedNftAccountsByOwner } from '@nfteyez/sol-rayz'
import { getTokenAccountsByMint } from 'utils/tokens'
export const ENDPOINTS: EndpointInfo[] = [
{
@ -130,6 +132,32 @@ export interface AlertRequest {
notifiAlertId: string | undefined
}
interface NFTFiles {
type: string
uri: string
}
interface NFTProperties {
category: string
files: NFTFiles[]
}
interface NFTData {
image: string
name: string
description: string
properties: NFTProperties
collection: {
family: string
name: string
}
}
interface NFTWithMint {
val: NFTData
mint: string
tokenAddress: string
}
export type MangoStore = {
notificationIdCounter: number
notifications: Array<Notification>
@ -197,6 +225,13 @@ export type MangoStore = {
wallet: {
tokens: WalletToken[] | any[]
pfp: ProfilePicture | undefined
nfts: {
data: NFTWithMint[] | []
initialLoad: boolean
loading: boolean
accounts: any[]
loadingTransaction: boolean
}
}
settings: {
uiLocked: boolean
@ -211,6 +246,12 @@ export type MangoStore = {
actions: {
fetchWalletTokens: (wallet: Wallet) => void
fetchProfilePicture: (wallet: Wallet) => void
fetchNfts: (
connection: Connection,
walletPk: PublicKey | null,
offset?: number
) => void
fetchNftAccounts: (connection: Connection, walletPk: PublicKey) => void
fetchAllMangoAccounts: (wallet: Wallet) => Promise<void>
fetchMangoGroup: () => Promise<void>
fetchTradeHistory: () => void
@ -341,6 +382,13 @@ const useMangoStore = create<
wallet: {
tokens: [],
pfp: undefined,
nfts: {
data: [],
initialLoad: false,
loading: false,
accounts: [],
loadingTransaction: false,
},
},
settings: {
uiLocked: true,
@ -423,6 +471,82 @@ const useMangoStore = create<
console.log('Could not get profile picture', e)
}
},
async fetchNftAccounts(connection: Connection, walletPk: PublicKey) {
const set = get().set
try {
const nfts = await getParsedNftAccountsByOwner({
publicAddress: walletPk.toBase58(),
connection: connection,
})
const data = Object.keys(nfts)
.map((key) => nfts[key])
.filter((data) => data.primarySaleHappened)
set((state) => {
state.wallet.nfts.accounts = data
})
} catch (error) {
console.log(error)
}
},
async fetchNfts(
connection: Connection,
walletPk: PublicKey,
offset = 0
) {
const set = get().set
set((state) => {
state.wallet.nfts.loading = true
})
try {
const nftAccounts = get().wallet.nfts.accounts
const loadedNfts = get().wallet.nfts.data
const arr: NFTWithMint[] = []
if (loadedNfts.length < nftAccounts.length) {
for (let i = offset; i < offset + 9; i++) {
try {
const response = await fetch(nftAccounts[i].data.uri)
const val = await response.json()
const tokenAccounts = await getTokenAccountsByMint(
connection,
nftAccounts[i].mint
)
arr.push({
val,
mint: nftAccounts[i].mint,
tokenAddress: tokenAccounts
.find(
(x) =>
x.account.owner.toBase58() === walletPk.toBase58()
)!
.publicKey.toBase58(),
})
} catch (e) {
console.log(e)
}
}
}
if (loadedNfts.length === 0) {
set((state) => {
state.wallet.nfts.data = arr
state.wallet.nfts.initialLoad = true
state.wallet.nfts.loading = false
})
} else {
set((state) => {
state.wallet.nfts.data = [...loadedNfts, ...arr]
state.wallet.nfts.loading = false
})
}
} catch (error) {
notify({
title: 'Unable to fetch nfts',
description: '',
})
set((state) => {
state.wallet.nfts.loading = false
})
}
},
async fetchAllMangoAccounts(wallet) {
const set = get().set
const mangoGroup = get().selectedMangoGroup.current

View File

@ -1,5 +1,5 @@
import { TokenAccountLayout } from '@blockworks-foundation/mango-client'
import { PublicKey } from '@solana/web3.js'
import { PublicKey, Connection } from '@solana/web3.js'
export const TOKEN_PROGRAM_ID = new PublicKey(
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
@ -22,3 +22,28 @@ export function parseTokenAccountData(data: Buffer): {
amount,
}
}
export async function getTokenAccountsByMint(
connection: Connection,
mint: string
): Promise<ProgramAccount<any>[]> {
const results = await connection.getProgramAccounts(TOKEN_PROGRAM_ID, {
filters: [
{
dataSize: 165,
},
{
memcmp: {
offset: 0,
bytes: mint,
},
},
],
})
return results.map((r) => {
const publicKey = r.pubkey
const data = Buffer.from(r.account.data)
const account = parseTokenAccountData(data)
return { publicKey, account }
})
}

2278
yarn.lock

File diff suppressed because it is too large Load Diff