Skip to content

Commit

Permalink
Create / Update Token Metadata (#866)
Browse files Browse the repository at this point in the history
* Create and Update token metadata proposal

* add MetadataCreationModal

* Metadata is deprecated on mpl-token-metadata v2

* Metadata is deprecated on mpl-token-metadata v2

* Civic Plugin: Simplify gateway provider config (#870)

* Metadata is deprecated on mpl-token-metadata v2

* fix

* fixed logic about making sol treasury and selecting correct governance

* yarn

* connected bundlr in devnet. made bundlr.fund stable

* bundlr getPrice should be called as close with uploading as possible

* calculate with buffer

Co-authored-by: Daniel Kelleher <[email protected]>
Co-authored-by: Sebastian Bor <[email protected]>
  • Loading branch information
3 people authored Jul 21, 2022
1 parent 5e77d7e commit 76f6835
Show file tree
Hide file tree
Showing 17 changed files with 3,283 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'
import { useForm, Controller } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'
import { Metadata } from '@metaplex-foundation/mpl-token-metadata'
import { deprecated } from '@metaplex-foundation/mpl-token-metadata'
import axios from 'axios'

import { updateUserInput, validateSolAddress } from '@utils/formValidation'
Expand Down Expand Up @@ -60,9 +60,9 @@ async function enrichItemInfo(item, uri) {
}

async function enrichCollectionInfo(connection, collectionKey) {
const {
data: { data: collectionData },
} = await Metadata.findByMint(connection, collectionKey)
const data = await deprecated.Metadata.findByMint(connection, collectionKey)

const collectionData = data!.data!.data

return enrichItemInfo(
{
Expand All @@ -74,7 +74,10 @@ async function enrichCollectionInfo(connection, collectionKey) {
}

async function getNFTCollectionInfo(connection, collectionKey) {
const { data: result } = await Metadata.findByMint(connection, collectionKey)
const { data: result } = await deprecated.Metadata.findByMint(
connection,
collectionKey
)
console.log('NFT findByMint result', result)
if (result?.collection?.verified && result.collection?.key) {
// here we were given a child of the collection (hence the "collection" property is present)
Expand All @@ -87,8 +90,8 @@ async function getNFTCollectionInfo(connection, collectionKey) {
return collectionInfo
} else {
// assume we've been given the collection address already, so we need to go find it's children
const children = await Metadata.findMany(connection, {
updateAuthority: result.updateAuthority,
const children = await deprecated.Metadata.findMany(connection, {
updateAuthority: result!.updateAuthority,
})

const verifiedCollections = filterAndMapVerifiedCollections(children)
Expand Down Expand Up @@ -337,7 +340,7 @@ export default function AddNFTCollectionForm({
throw new Error('No valid wallet connected')
}

const ownedNfts = await Metadata.findDataByOwner(
const ownedNfts = await deprecated.Metadata.findDataByOwner(
connection.current,
wallet.publicKey
)
Expand Down
10 changes: 6 additions & 4 deletions components/TreasuryAccount/DepositNFTAddress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Input from '@components/inputs/Input'
import { tryParseKey } from '@tools/validators/pubkey'
import { debounce } from '@utils/debounce'
import useWalletStore from 'stores/useWalletStore'
import { Metadata } from '@metaplex-foundation/mpl-token-metadata'
import { deprecated } from '@metaplex-foundation/mpl-token-metadata'
import axios from 'axios'
import { notify } from '@utils/notifications'
import Loading from '@components/Loading'
Expand Down Expand Up @@ -35,7 +35,9 @@ const DepositNFTAddress = ({ additionalBtns }: { additionalBtns?: any }) => {
mint: '',
})
const [isLoading, setIsLoading] = useState(false)
const [nftMetaData, setNftMetaData] = useState<Metadata | null>(null)
const [nftMetaData, setNftMetaData] = useState<deprecated.Metadata | null>(
null
)
const [isInvalidMint, setIsInvalidMint] = useState(false)
const [formErrors, setFormErrors] = useState({})
const [imgUrl, setImgUrl] = useState('')
Expand Down Expand Up @@ -97,8 +99,8 @@ const DepositNFTAddress = ({ additionalBtns }: { additionalBtns?: any }) => {
if (pubKey) {
setIsLoading(true)
try {
const metadataPDA = await Metadata.getPDA(pubKey)
const tokenMetadata = await Metadata.load(
const metadataPDA = await deprecated.Metadata.getPDA(pubKey)
const tokenMetadata = await deprecated.Metadata.load(
connection.current,
metadataPDA
)
Expand Down
156 changes: 156 additions & 0 deletions components/inputs/GovernedAccountSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import Select from '@components/inputs/Select'
import { Governance, GovernanceAccountType } from '@solana/spl-governance'
import { ProgramAccount } from '@solana/spl-governance'
import {
getMintAccountLabelInfo,
getSolAccountLabel,
getTokenAccountLabelInfo,
} from '@utils/tokens'
import React, { useEffect } from 'react'
import { getProgramName } from '@components/instructions/programs/names'
import { AssetAccount } from '@utils/uiTypes/assets'

const GovernedAccountSelect = ({
onChange,
value,
error,
governedAccounts = [],
shouldBeGoverned,
governance,
label,
noMaxWidth,
autoselectFirst = true,
}: {
onChange
value
error?
governedAccounts: AssetAccount[]
shouldBeGoverned?
governance?: ProgramAccount<Governance> | null | undefined
label?
noMaxWidth?: boolean
autoselectFirst?: boolean
}) => {
function getLabel(value: AssetAccount) {
if (value) {
const accountType = value.governance.account.accountType
if (value.isSol || value.isToken) {
return getTokenAccountLabelComponent(
value.isSol
? getSolAccountLabel(value)
: getTokenAccountLabelInfo(value)
)
} else {
switch (accountType) {
case GovernanceAccountType.MintGovernanceV1:
case GovernanceAccountType.MintGovernanceV2:
return getMintAccountLabelComponent(getMintAccountLabelInfo(value))
case GovernanceAccountType.ProgramGovernanceV1:
case GovernanceAccountType.ProgramGovernanceV2:
return getProgramAccountLabel(value.governance)
default:
return value.governance.account.governedAccount.toBase58()
}
}
} else {
return null
}
}
//TODO refactor both methods (getMintAccountLabelComponent, getTokenAccountLabelComponent) make it more common
function getMintAccountLabelComponent({
account,
tokenName,
mintAccountName,
amount,
imgUrl,
}) {
return (
<div className="break-all text-fgd-1">
{account && <div className="mb-0.5">{account}</div>}
<div className="mb-2">{mintAccountName}</div>
<div className="space-y-0.5 text-xs text-fgd-3">
{tokenName && (
<div className="flex items-center">
Token: <img className="flex-shrink-0 h-4 mx-1 w-4" src={imgUrl} />
{tokenName}
</div>
)}
<div>Supply: {amount}</div>
</div>
</div>
)
}
function getTokenAccountLabelComponent({
tokenAccount,
tokenAccountName,
tokenName,
amount,
}) {
return (
<div className="break-all text-fgd-1 ">
{tokenAccountName && <div className="mb-0.5">{tokenAccountName}</div>}
<div className="mb-2 text-fgd-3 text-xs">{tokenAccount}</div>
<div className="flex space-x-3 text-xs text-fgd-3">
{tokenName && (
<div className="flex items-center">
Token:
<span className="ml-1 text-fgd-1">{tokenName}</span>
</div>
)}
<div>
Bal:<span className="ml-1 text-fgd-1">{amount}</span>
</div>
</div>
</div>
)
}
function getProgramAccountLabel(val: ProgramAccount<Governance>) {
const name = val ? getProgramName(val.account.governedAccount) : ''
return (
<div className="flex flex-col">
{name && <div>{name}</div>}
<div>{val?.account?.governedAccount?.toBase58()}</div>
</div>
)
}
useEffect(() => {
if (governedAccounts.length == 1 && autoselectFirst) {
//wait for microtask queue to be empty
setTimeout(() => {
onChange(governedAccounts[0])
})
}
}, [JSON.stringify(governedAccounts)])
return (
<Select
label={label}
onChange={onChange}
componentLabel={getLabel(value)}
placeholder="Please select..."
value={value?.governance?.account.governedAccount.toBase58()}
error={error}
noMaxWidth={noMaxWidth}
>
{governedAccounts
.filter((x) =>
!shouldBeGoverned
? !shouldBeGoverned
: x?.governance?.pubkey.toBase58() ===
governance?.pubkey?.toBase58()
)
.map((acc) => {
return (
<Select.Option
className="border-red"
key={acc.pubkey.toBase58()}
value={acc}
>
{getLabel(acc)}
</Select.Option>
)
})}
</Select>
)
}

export default GovernedAccountSelect
8 changes: 4 additions & 4 deletions components/instructions/instructionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ExecuteInstructionButton, PlayState } from './ExecuteInstructionButton'
import { ProgramAccount } from '@solana/spl-governance'
import InspectorButton from '@components/explorer/inspectorButton'
import { FlagInstructionErrorButton } from './FlagInstructionErrorButton'
import { Metadata } from '@metaplex-foundation/mpl-token-metadata'
import { deprecated } from '@metaplex-foundation/mpl-token-metadata'
import axios from 'axios'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import tokenService from '@utils/services/token'
Expand Down Expand Up @@ -88,12 +88,12 @@ export default function InstructionCard({
const mint = tokenAccount?.account.mint
if (mint) {
try {
const metadataPDA = await Metadata.getPDA(mint)
const tokenMetadata = await Metadata.load(
const metadataPDA = await deprecated.Metadata.getPDA(mint)
const tokenMetadata = await deprecated.Metadata.load(
connection.current,
metadataPDA
)
const url = (await axios.get(tokenMetadata.data.data.uri)).data
const url = (await axios.get(tokenMetadata?.data!.data.uri)).data
setNftImgUrl(url.image)
} catch (e) {
console.log(e)
Expand Down
10 changes: 10 additions & 0 deletions hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@ export default function useGovernanceAssets() {
isVisible: canUseTransferInstruction,
},
{
id: Instructions.CreateTokenMetadata,
name: 'Create Token Metadata',
isVisible: canUseAuthorityInstruction,
},
{
id: Instructions.UpdateTokenMetadata,
name: 'Update Token Metadata',
isVisible: canUseAuthorityInstruction,
},
{
id: Instructions.SagaPreOrder,
name: 'Pre-order Saga Phone',
isVisible: canUseTokenTransferInstruction,
Expand Down
22 changes: 14 additions & 8 deletions hooks/useVotingPlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect } from 'react'
import useWalletStore from 'stores/useWalletStore'
import useRealm from '@hooks/useRealm'
import { getNfts } from '@utils/tokens'
import { Metadata } from '@metaplex-foundation/mpl-token-metadata'
import { deprecated } from '@metaplex-foundation/mpl-token-metadata'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import useNftPluginStore from 'NftVotePlugin/store/nftPluginStore'
import useSwitchboardPluginStore from 'SwitchboardVotePlugin/store/switchboardStore'
Expand Down Expand Up @@ -113,7 +113,10 @@ export function useVotingPlugins() {
await Promise.all(
nfts.map((x) => getIsFromCollection(x.mint, x.tokenAddress))
)
).filter((x) => x) as { metadata: Metadata; tokenAddress: PublicKey }[]
).filter((x) => x) as {
metadata: deprecated.Metadata
tokenAddress: PublicKey
}[]
const nftsWithMeta = votingNfts.map((x) => {
const nft = nfts.find(
(nft) => nft.tokenAddress === x.tokenAddress.toBase58()
Expand Down Expand Up @@ -312,16 +315,19 @@ export function useVotingPlugins() {
}
}
const getIsFromCollection = async (mint: string, tokenAddress: string) => {
const metadataAccount = await Metadata.getPDA(mint)
const metadata = await Metadata.load(connection.current, metadataAccount)
const metadataAccount = await deprecated.Metadata.getPDA(mint)
const metadata = await deprecated.Metadata.load(
connection.current,
metadataAccount
)
return (
!!(
metadata.data.collection?.key &&
usedCollectionsPks.includes(metadata.data.collection?.key) &&
metadata.data.collection.verified
metadata.data!.collection?.key &&
usedCollectionsPks.includes(metadata.data!.collection?.key) &&
metadata.data!.collection.verified
) && {
tokenAddress: new PublicKey(tokenAddress),
metadata: metadata as Metadata,
metadata: metadata as deprecated.Metadata,
}
)
}
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dependencies": {
"@blockworks-foundation/mango-client": "^3.6.7",
"@blockworks-foundation/voter-stake-registry-client": "^0.2.3",
"@bundlr-network/client": "^0.7.15",
"@cardinal/namespaces-components": "^2.5.5",
"@castlefinance/vault-core": "^0.1.3",
"@castlefinance/vault-sdk": "^2.1.2",
Expand All @@ -40,7 +41,8 @@
"@heroicons/react": "^1.0.1",
"@hookform/resolvers": "^2.8.10",
"@marinade.finance/marinade-ts-sdk": "^2.0.9",
"@metaplex-foundation/mpl-token-metadata": "^1.2.5",
"@metaplex-foundation/js": "^0.12.0",
"@metaplex-foundation/mpl-token-metadata": "~2.0",
"@mithraic-labs/serum-remote": "^0.0.1-rc.16",
"@next/bundle-analyzer": "^12.1.5",
"@nfteyez/sol-rayz": "^0.10.2",
Expand Down Expand Up @@ -86,6 +88,7 @@
"rc-slider": "^9.7.5",
"react": "^18.1.0",
"react-dom": "^18.0.0",
"react-dropzone": "^14.2.2",
"react-headless-pagination": "^0.1.0",
"react-hook-form": "^7.31.3",
"react-markdown": "^7.0.0",
Expand Down
Loading

1 comment on commit 76f6835

@vercel
Copy link

@vercel vercel bot commented on 76f6835 Jul 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.