Merge pull request #123 from blockworks-foundation/metaplex-load-nfts
Metaplex load nfts
This commit is contained in:
commit
d228b1f467
|
@ -9,7 +9,7 @@ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|||
disabled?: boolean
|
||||
prefixClassname?: string
|
||||
wrapperClassName?: string
|
||||
error?: boolean
|
||||
hasError?: boolean
|
||||
prefix?: string
|
||||
prefixClassName?: string
|
||||
suffix?: string
|
||||
|
@ -22,7 +22,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
|||
onChange,
|
||||
maxLength,
|
||||
className,
|
||||
error,
|
||||
hasError,
|
||||
wrapperClassName = 'w-full',
|
||||
disabled,
|
||||
prefix,
|
||||
|
@ -40,10 +40,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
|||
</div>
|
||||
) : null}
|
||||
<input
|
||||
{...props}
|
||||
className={`${className} default-transition h-12 w-full flex-1 rounded-md border bg-th-input-bkg px-3 text-base
|
||||
text-th-fgd-1 ${
|
||||
error ? 'border-th-down' : 'border-th-input-border'
|
||||
hasError ? 'border-th-down' : 'border-th-input-border'
|
||||
} focus:outline-none
|
||||
md:hover:border-th-input-border-hover
|
||||
${
|
||||
|
|
|
@ -613,7 +613,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('oracle')} />
|
||||
<Input
|
||||
error={formErrors.oraclePk !== undefined}
|
||||
hasError={formErrors.oraclePk !== undefined}
|
||||
type="text"
|
||||
value={advForm.oraclePk}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -632,7 +632,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('token-index')} />
|
||||
<Input
|
||||
error={formErrors.tokenIndex !== undefined}
|
||||
hasError={formErrors.tokenIndex !== undefined}
|
||||
type="number"
|
||||
value={advForm.tokenIndex.toString()}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -651,7 +651,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('openbook-market-external')} />
|
||||
<Input
|
||||
error={
|
||||
hasError={
|
||||
formErrors.openBookMarketExternalPk !==
|
||||
undefined
|
||||
}
|
||||
|
@ -676,7 +676,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('base-bank')} />
|
||||
<Input
|
||||
error={formErrors.baseBankPk !== undefined}
|
||||
hasError={formErrors.baseBankPk !== undefined}
|
||||
type="text"
|
||||
value={advForm.baseBankPk.toString()}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -695,7 +695,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('quote-bank')} />
|
||||
<Input
|
||||
error={formErrors.quoteBankPk !== undefined}
|
||||
hasError={formErrors.quoteBankPk !== undefined}
|
||||
type="text"
|
||||
value={advForm.quoteBankPk.toString()}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -714,7 +714,9 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('openbook-program')} />
|
||||
<Input
|
||||
error={formErrors.openBookProgram !== undefined}
|
||||
hasError={
|
||||
formErrors.openBookProgram !== undefined
|
||||
}
|
||||
type="text"
|
||||
value={advForm.openBookProgram.toString()}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -736,7 +738,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('market-name')} />
|
||||
<Input
|
||||
error={formErrors.marketName !== undefined}
|
||||
hasError={formErrors.marketName !== undefined}
|
||||
type="text"
|
||||
value={advForm.marketName.toString()}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -755,7 +757,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('proposal-title')} />
|
||||
<Input
|
||||
error={formErrors.proposalTitle !== undefined}
|
||||
hasError={formErrors.proposalTitle !== undefined}
|
||||
type="text"
|
||||
value={advForm.proposalTitle.toString()}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -777,7 +779,7 @@ const ListToken = () => {
|
|||
<div>
|
||||
<Label text={t('proposal-des')} />
|
||||
<Input
|
||||
error={
|
||||
hasError={
|
||||
formErrors.proposalDescription !== undefined
|
||||
}
|
||||
type="text"
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
getVoteRecordAddress,
|
||||
} from '@solana/spl-governance'
|
||||
import { VoteCountdown } from './VoteCountdown'
|
||||
import { MintInfo } from '@solana/spl-token'
|
||||
import { RawMint } from '@solana/spl-token'
|
||||
import VoteResults from './VoteResult'
|
||||
import QuorumProgress from './VoteProgress'
|
||||
import GovernanceStore from '@store/governanceStore'
|
||||
|
@ -40,7 +40,7 @@ const ProposalCard = ({
|
|||
mangoMint,
|
||||
}: {
|
||||
proposal: ProgramAccount<Proposal>
|
||||
mangoMint: MintInfo
|
||||
mangoMint: RawMint
|
||||
}) => {
|
||||
const { t } = useTranslation('governance')
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
|
|
|
@ -3,7 +3,7 @@ import GovernanceStore from '@store/governanceStore'
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { isInCoolOffTime } from 'utils/governance/proposals'
|
||||
import { MintInfo } from '@solana/spl-token'
|
||||
import { RawMint } from '@solana/spl-token'
|
||||
import { MANGO_MINT } from 'utils/constants'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -27,7 +27,7 @@ const Vote = () => {
|
|||
const loadingVoter = GovernanceStore((s) => s.loadingVoter)
|
||||
const loadingRealm = GovernanceStore((s) => s.loadingRealm)
|
||||
|
||||
const [mangoMint, setMangoMint] = useState<MintInfo | null>(null)
|
||||
const [mangoMint, setMangoMint] = useState<RawMint | null>(null)
|
||||
const [votingProposals, setVotingProposals] = useState<
|
||||
ProgramAccount<Proposal>[]
|
||||
>([])
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
InformationCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { Governance, ProgramAccount, Proposal } from '@solana/spl-governance'
|
||||
import { MintInfo } from '@solana/spl-token'
|
||||
import { RawMint } from '@solana/spl-token'
|
||||
import GovernanceStore from '@store/governanceStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { getMintMaxVoteWeight } from 'utils/governance/proposals'
|
||||
|
@ -13,7 +13,7 @@ import { fmtTokenAmount } from 'utils/governance/tools'
|
|||
type Props = {
|
||||
governance: ProgramAccount<Governance>
|
||||
proposal: ProgramAccount<Proposal>
|
||||
communityMint: MintInfo
|
||||
communityMint: RawMint
|
||||
}
|
||||
|
||||
const QuorumProgress = ({ governance, proposal, communityMint }: Props) => {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Proposal } from '@solana/spl-governance'
|
||||
import VoteResultsBar from './VoteResultBar'
|
||||
import { fmtTokenAmount } from 'utils/governance/tools'
|
||||
import { MintInfo } from '@solana/spl-token'
|
||||
import { RawMint } from '@solana/spl-token'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
type VoteResultsProps = {
|
||||
proposal: Proposal
|
||||
communityMint: MintInfo
|
||||
communityMint: RawMint
|
||||
}
|
||||
|
||||
const VoteResults = ({ proposal, communityMint }: VoteResultsProps) => {
|
||||
|
|
|
@ -8,14 +8,18 @@ import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'
|
|||
import { notify } from 'utils/notifications'
|
||||
import { MANGO_DATA_API_URL } from 'utils/constants'
|
||||
|
||||
const ImgWithLoader = (props: { className: string; src: string }) => {
|
||||
const ImgWithLoader = (props: {
|
||||
className: string
|
||||
src: string
|
||||
alt: string
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
return (
|
||||
<div className="relative">
|
||||
{isLoading && (
|
||||
<PhotoIcon className="absolute left-1/2 top-1/2 z-10 h-1/4 w-1/4 -translate-x-1/2 -translate-y-1/2 animate-pulse text-th-fgd-4" />
|
||||
)}
|
||||
<img {...props} onLoad={() => setIsLoading(false)} alt="" />
|
||||
<img {...props} onLoad={() => setIsLoading(false)} alt={props.alt} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -158,17 +162,18 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
|
|||
{nfts.length > 0 ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="mb-4 grid w-full grid-flow-row grid-cols-3 gap-3">
|
||||
{nfts.map((n) => (
|
||||
{nfts.map((n, i) => (
|
||||
<button
|
||||
className={`default-transition col-span-1 flex items-center justify-center rounded-md border bg-th-bkg-2 py-3 sm:py-4 md:hover:bg-th-bkg-3 ${
|
||||
selectedProfile === n.image
|
||||
? 'border-th-active'
|
||||
: 'border-th-bkg-3'
|
||||
}`}
|
||||
key={n.image}
|
||||
key={n.image + i}
|
||||
onClick={() => setSelectedProfile(n.image)}
|
||||
>
|
||||
<ImgWithLoader
|
||||
alt={n.name}
|
||||
className="h-16 w-16 flex-shrink-0 rounded-full sm:h-20 sm:w-20"
|
||||
src={n.image}
|
||||
/>
|
||||
|
@ -178,7 +183,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
|
|||
</div>
|
||||
) : nftsLoading ? (
|
||||
<div className="mb-4 grid w-full grid-flow-row grid-cols-3 gap-4">
|
||||
{[...Array(9)].map((i) => (
|
||||
{[...Array(9)].map((x, i) => (
|
||||
<div
|
||||
className="col-span-1 h-[90px] animate-pulse rounded-md bg-th-bkg-3 sm:h-28"
|
||||
key={i}
|
||||
|
|
|
@ -159,7 +159,7 @@ const EditProfileForm = ({
|
|||
<Label text={t('profile:profile-name')} />
|
||||
<Input
|
||||
type="text"
|
||||
error={!!inputError.length}
|
||||
hasError={!!inputError.length}
|
||||
value={profileName}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
onChangeNameInput(e.target.value)
|
||||
|
|
|
@ -138,17 +138,6 @@ const ConnectedMenu = () => {
|
|||
</button>
|
||||
</Menu.Item>
|
||||
) : null}
|
||||
{/* <Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-active focus:outline-none"
|
||||
onClick={() => setShowProfileImageModal(true)}
|
||||
>
|
||||
<ProfileIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">
|
||||
{t('edit-profile-image')}
|
||||
</div>
|
||||
</button>
|
||||
</Menu.Item> */}
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="default-transition flex w-full flex-row items-center rounded-none py-0.5 font-normal focus:outline-none md:hover:cursor-pointer md:hover:text-th-fgd-1"
|
||||
|
|
|
@ -21,9 +21,11 @@
|
|||
"@blockworks-foundation/mango-v4": "^0.9.15",
|
||||
"@headlessui/react": "1.6.6",
|
||||
"@heroicons/react": "2.0.10",
|
||||
"@metaplex-foundation/js": "0.18.3",
|
||||
"@project-serum/anchor": "0.25.0",
|
||||
"@pythnetwork/client": "2.15.0",
|
||||
"@solana/spl-governance": "0.3.25",
|
||||
"@solana/spl-token": "0.3.7",
|
||||
"@solana/wallet-adapter-base": "0.9.20",
|
||||
"@solana/wallet-adapter-react": "0.15.32",
|
||||
"@solana/wallet-adapter-wallets": "0.19.11",
|
||||
|
@ -102,6 +104,8 @@
|
|||
"@solana/wallet-adapter-wallets>@solana/wallet-adapter-torus>@toruslabs/solana-embed>@toruslabs/base-controllers>@toruslabs/broadcast-channel>@toruslabs/eccrypto>secp256k1": true,
|
||||
"@solana/wallet-adapter-wallets>@solana/wallet-adapter-torus>@toruslabs/solana-embed>@toruslabs/base-controllers>ethereumjs-util>ethereum-cryptography>secp256k1": true,
|
||||
"@solana/wallet-adapter-wallets>@solana/wallet-adapter-torus>@toruslabs/solana-embed>@toruslabs/openlogin-jrpc>@toruslabs/openlogin-utils>keccak": true,
|
||||
"@metaplex-foundation/js>@bundlr-network/client>arbundles>secp256k1": true,
|
||||
"@metaplex-foundation/js>@bundlr-network/client>arbundles>keccak": true,
|
||||
"@solana/web3.js>bigint-buffer": false,
|
||||
"@solana/web3.js>rpc-websockets>bufferutil": true,
|
||||
"@solana/web3.js>rpc-websockets>utf-8-validate": true,
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
import EmptyWallet from '../utils/wallet'
|
||||
import { Notification, notify } from '../utils/notifications'
|
||||
import {
|
||||
fetchNftsFromHolaplexIndexer,
|
||||
getNFTsByOwner,
|
||||
getTokenAccountsByOwnerWithWrappedSol,
|
||||
TokenAccount,
|
||||
} from '../utils/tokens'
|
||||
|
@ -664,9 +664,9 @@ const mangoStore = create<MangoStore>()(
|
|||
state.wallet.nfts.loading = true
|
||||
})
|
||||
try {
|
||||
const data = await fetchNftsFromHolaplexIndexer(ownerPk)
|
||||
const nfts = await getNFTsByOwner(ownerPk, connection)
|
||||
set((state) => {
|
||||
state.wallet.nfts.data = data.nfts
|
||||
state.wallet.nfts.data = nfts
|
||||
state.wallet.nfts.loading = false
|
||||
})
|
||||
} catch (error) {
|
||||
|
|
|
@ -246,6 +246,7 @@ export interface SwapHistoryItem {
|
|||
export interface NFT {
|
||||
address: string
|
||||
image: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface PerpStatsItem {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BN } from '@coral-xyz/anchor'
|
||||
import { MintInfo } from '@solana/spl-token'
|
||||
import { Mint } from '@solana/spl-token'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { VsrClient } from '../voteStakeRegistryClient'
|
||||
|
||||
|
@ -55,7 +55,7 @@ export interface Deposit {
|
|||
}
|
||||
|
||||
export interface DepositWithMintAccount extends Deposit {
|
||||
mint: TokenProgramAccount<MintInfo>
|
||||
mint: TokenProgramAccount<Mint>
|
||||
index: number
|
||||
available: BN
|
||||
vestingRate: BN | null
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { MintInfo } from '@solana/spl-token'
|
||||
import { BN, EventParser } from '@coral-xyz/anchor'
|
||||
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
|
||||
import {
|
||||
|
@ -17,6 +16,7 @@ import {
|
|||
tryGetRegistrar,
|
||||
tryGetVoter,
|
||||
} from '../accounts/vsrAccounts'
|
||||
import { RawMint } from '@solana/spl-token'
|
||||
|
||||
type Event = {
|
||||
depositEntryIndex: number
|
||||
|
@ -67,7 +67,7 @@ export const getDeposits = async ({
|
|||
])
|
||||
|
||||
const mintCfgs = existingRegistrar?.votingMints || []
|
||||
const mints: { [key: string]: TokenProgramAccount<MintInfo> | undefined } = {}
|
||||
const mints: { [key: string]: TokenProgramAccount<RawMint> | undefined } = {}
|
||||
let votingPower = new BN(0)
|
||||
let votingPowerFromDeposits = new BN(0)
|
||||
let deposits: DepositWithMintAccount[] = []
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from '@solana/spl-governance'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import dayjs from 'dayjs'
|
||||
import { MintInfo } from '@solana/spl-token'
|
||||
import { RawMint } from '@solana/spl-token'
|
||||
|
||||
export const isInCoolOffTime = (
|
||||
proposal: Proposal | undefined,
|
||||
|
@ -38,7 +38,7 @@ export const isInCoolOffTime = (
|
|||
|
||||
/** Returns max VoteWeight for given mint and max source */
|
||||
export function getMintMaxVoteWeight(
|
||||
mint: MintInfo,
|
||||
mint: RawMint,
|
||||
maxVoteWeightSource: MintMaxVoteWeightSource
|
||||
) {
|
||||
if (maxVoteWeightSource.type === MintMaxVoteWeightSourceType.SupplyFraction) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from '@solana/spl-governance'
|
||||
import { Connection, PublicKey } from '@solana/web3.js'
|
||||
import { TokenProgramAccount } from './accounts/vsrAccounts'
|
||||
import { u64, MintLayout, MintInfo } from '@solana/spl-token'
|
||||
import { MintLayout, RawMint } from '@solana/spl-token'
|
||||
import BN from 'bn.js'
|
||||
|
||||
export async function fetchRealm({
|
||||
|
@ -57,7 +57,7 @@ export function arrayToRecord<T>(
|
|||
export async function tryGetMint(
|
||||
connection: Connection,
|
||||
publicKey: PublicKey
|
||||
): Promise<TokenProgramAccount<MintInfo> | undefined> {
|
||||
): Promise<TokenProgramAccount<RawMint> | undefined> {
|
||||
try {
|
||||
const result = await connection.getAccountInfo(publicKey)
|
||||
const data = Buffer.from(result!.data)
|
||||
|
@ -75,22 +75,8 @@ export async function tryGetMint(
|
|||
}
|
||||
}
|
||||
|
||||
export function parseMintAccountData(data: Buffer): MintInfo {
|
||||
export function parseMintAccountData(data: Buffer): RawMint {
|
||||
const mintInfo = MintLayout.decode(data)
|
||||
if (mintInfo.mintAuthorityOption === 0) {
|
||||
mintInfo.mintAuthority = null
|
||||
} else {
|
||||
mintInfo.mintAuthority = new PublicKey(mintInfo.mintAuthority)
|
||||
}
|
||||
|
||||
mintInfo.supply = u64.fromBuffer(mintInfo.supply)
|
||||
mintInfo.isInitialized = mintInfo.isInitialized != 0
|
||||
|
||||
if (mintInfo.freezeAuthorityOption === 0) {
|
||||
mintInfo.freezeAuthority = null
|
||||
} else {
|
||||
mintInfo.freezeAuthority = new PublicKey(mintInfo.freezeAuthority)
|
||||
}
|
||||
return mintInfo
|
||||
}
|
||||
|
||||
|
|
106
utils/tokens.ts
106
utils/tokens.ts
|
@ -1,6 +1,18 @@
|
|||
import { PublicKey, Connection } from '@solana/web3.js'
|
||||
import { TokenInstructions } from '@project-serum/serum'
|
||||
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
getAssociatedTokenAddress,
|
||||
toUiDecimals,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
Metaplex,
|
||||
Nft,
|
||||
Sft,
|
||||
SftWithToken,
|
||||
NftWithToken,
|
||||
Metadata,
|
||||
JsonMetadata,
|
||||
} from '@metaplex-foundation/js'
|
||||
|
||||
export class TokenAccount {
|
||||
publicKey!: PublicKey
|
||||
|
@ -26,6 +38,16 @@ export class TokenAccount {
|
|||
}
|
||||
}
|
||||
|
||||
type RawNft = Nft | Sft | SftWithToken | NftWithToken
|
||||
type NftWithATA = RawNft & {
|
||||
owner: null | PublicKey
|
||||
tokenAccountAddress: null | PublicKey
|
||||
}
|
||||
|
||||
function exists<T>(item: T | null | undefined): item is T {
|
||||
return !!item
|
||||
}
|
||||
|
||||
export async function getTokenAccountsByOwnerWithWrappedSol(
|
||||
connection: Connection,
|
||||
owner: PublicKey
|
||||
|
@ -63,43 +85,59 @@ export async function getTokenAccountsByOwnerWithWrappedSol(
|
|||
return [solAccount].concat(tokenAccounts)
|
||||
}
|
||||
|
||||
export const fetchNftsFromHolaplexIndexer = async (owner: PublicKey) => {
|
||||
const result = await fetch('https://graph.holaplex.com/v1', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query nfts($owners: [PublicKey!]) {
|
||||
nfts(
|
||||
owners: $owners,
|
||||
limit: 10000, offset: 0) {
|
||||
name
|
||||
mintAddress
|
||||
address
|
||||
image
|
||||
updateAuthorityAddress
|
||||
collection {
|
||||
creators {
|
||||
verified
|
||||
address
|
||||
}
|
||||
mintAddress
|
||||
}
|
||||
const enhanceNFT = (nft: NftWithATA) => {
|
||||
return {
|
||||
image: nft.json?.image || '',
|
||||
name: nft.json?.name || '',
|
||||
address: nft.metadataAddress.toBase58(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
function loadNft(
|
||||
nft: Metadata<JsonMetadata<string>> | Nft | Sft,
|
||||
connection: Connection
|
||||
) {
|
||||
const metaplex = new Metaplex(connection)
|
||||
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
owners: [owner.toBase58()],
|
||||
},
|
||||
}),
|
||||
return Promise.race([
|
||||
metaplex
|
||||
.nfts()
|
||||
// @ts-ignore
|
||||
.load({ metadata: nft })
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
return null
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
export async function getNFTsByOwner(owner: PublicKey, connection: Connection) {
|
||||
const metaplex = new Metaplex(connection)
|
||||
|
||||
const rawNfts = await metaplex.nfts().findAllByOwner({
|
||||
owner,
|
||||
})
|
||||
|
||||
const body = await result.json()
|
||||
return body.data
|
||||
const nfts = await Promise.all(
|
||||
rawNfts.map((nft) => loadNft(nft, connection))
|
||||
).then((nfts) =>
|
||||
Promise.all(
|
||||
nfts.filter(exists).map(async (nft) => ({
|
||||
...nft,
|
||||
owner,
|
||||
tokenAccountAddress: await getAssociatedTokenAddress(
|
||||
nft.mint.address,
|
||||
owner,
|
||||
true
|
||||
).catch((e) => {
|
||||
console.error(e)
|
||||
return null
|
||||
}),
|
||||
}))
|
||||
)
|
||||
)
|
||||
|
||||
return nfts.map(enhanceNFT)
|
||||
}
|
||||
|
||||
export const formatTokenSymbol = (symbol: string) =>
|
||||
|
|
Loading…
Reference in New Issue