Added code to retrieve Metaplex metadata for NFTs
This commit is contained in:
parent
c12d8ea20d
commit
209b2c47df
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import {
|
||||
|
@ -15,19 +15,20 @@ import {
|
|||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import { abbreviateAddress, copyToClipboard } from '../utils'
|
||||
import WalletSelect from './WalletSelect'
|
||||
import { WalletIcon } from './icons'
|
||||
import { ProfileIcon, WalletIcon } from './icons'
|
||||
import AccountsModal from './AccountsModal'
|
||||
import { useEffect } from 'react'
|
||||
import SettingsModal from './SettingsModal'
|
||||
import mango_hero from '../components/assets/mango_heroes.jpg'
|
||||
|
||||
const ConnectWalletButton = () => {
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const nfts = useMangoStore((s) => s.settings.nfts)
|
||||
const set = useMangoStore((s) => s.set)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [showSettingsModal, setShowSettingsModal] = useState(false)
|
||||
const [selectedWallet, setSelectedWallet] = useState(DEFAULT_PROVIDER.url)
|
||||
const [imageUrl, setImageUrl] = useState('')
|
||||
const [savedProviderUrl] = useLocalStorageState(
|
||||
PROVIDER_LOCAL_STORAGE_KEY,
|
||||
DEFAULT_PROVIDER.url
|
||||
|
@ -38,6 +39,24 @@ const ConnectWalletButton = () => {
|
|||
setSelectedWallet(savedProviderUrl)
|
||||
}, [savedProviderUrl])
|
||||
|
||||
useEffect(() => {
|
||||
if (nfts.length == 0) return
|
||||
const nft = nfts[0]
|
||||
|
||||
try {
|
||||
fetch(nft).then(async (_) => {
|
||||
try {
|
||||
const data = await _.json()
|
||||
setImageUrl(data['image'] as string)
|
||||
} catch (ex) {
|
||||
console.error('Error trying to parse JSON: ' + ex)
|
||||
}
|
||||
})
|
||||
} catch (ex) {
|
||||
console.error('Error trying to fetch Arweave metadata: ' + ex)
|
||||
}
|
||||
})
|
||||
|
||||
const handleWalletConect = () => {
|
||||
wallet.connect()
|
||||
set((state) => {
|
||||
|
@ -56,12 +75,16 @@ const ConnectWalletButton = () => {
|
|||
<div className="relative">
|
||||
<Menu.Button
|
||||
className="bg-th-bkg-4 flex items-center justify-center rounded-full w-10 h-10 text-white focus:outline-none hover:bg-th-bkg-4 hover:text-th-fgd-3"
|
||||
style={{
|
||||
backgroundImage: `url(${mango_hero.src})`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
style={
|
||||
imageUrl != ''
|
||||
? {
|
||||
backgroundImage: `url(${imageUrl})`,
|
||||
backgroundSize: 'cover',
|
||||
}
|
||||
: null
|
||||
}
|
||||
>
|
||||
{/* <ProfileIcon className="h-6 w-6" /> */}
|
||||
{imageUrl == '' ? <ProfileIcon className="h-6 w-6" /> : null}
|
||||
{/* <NewProfileIcon className="h-6 w-6" src={mango_hero.src} /> */}
|
||||
</Menu.Button>
|
||||
<Menu.Items className="bg-th-bkg-1 mt-2 p-1 absolute right-0 shadow-lg outline-none rounded-md w-48 z-20">
|
||||
|
|
|
@ -107,6 +107,7 @@ export default function useWallet() {
|
|||
actions.reloadOrders()
|
||||
actions.fetchTradeHistory()
|
||||
actions.fetchWalletTokens()
|
||||
actions.fetchProfilePicture()
|
||||
notify({
|
||||
title: 'Wallet connected',
|
||||
description:
|
||||
|
@ -144,6 +145,7 @@ export default function useWallet() {
|
|||
useInterval(() => {
|
||||
if (connected && mangoAccount) {
|
||||
actions.fetchWalletTokens()
|
||||
actions.fetchProfilePicture()
|
||||
actions.fetchTradeHistory()
|
||||
}
|
||||
}, 90 * SECONDS)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -43,6 +43,7 @@
|
|||
"babel-plugin-import": "^1.13.3",
|
||||
"big.js": "^6.1.1",
|
||||
"bn.js": "^5.2.0",
|
||||
"borsh": "^0.6.0",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer-layout": "^1.2.0",
|
||||
"dayjs": "^1.10.4",
|
||||
|
|
|
@ -31,6 +31,11 @@ import {
|
|||
initialMarket,
|
||||
NODE_URL_KEY,
|
||||
} from '../components/SettingsModal'
|
||||
import { TOKEN_PROGRAM_ID } from '../utils/tokens'
|
||||
import { findProgramAddress } from '../utils/metaplex/utils'
|
||||
import * as borsh from 'borsh'
|
||||
import { Metadata, METADATA_SCHEMA } from '../utils/metaplex/models'
|
||||
import { METADATA_PREFIX } from '../utils/metaplex/types'
|
||||
|
||||
export const ENDPOINTS: EndpointInfo[] = [
|
||||
{
|
||||
|
@ -43,8 +48,8 @@ export const ENDPOINTS: EndpointInfo[] = [
|
|||
name: 'devnet',
|
||||
// url: 'https://mango.devnet.rpcpool.com',
|
||||
// websocket: 'https://mango.devnet.rpcpool.com',
|
||||
url: 'https://api.devnet.solana.com',
|
||||
websocket: 'https://api.devnet.solana.com',
|
||||
url: 'https://mango.rpcpool.com',
|
||||
websocket: 'https://mango.rpcpool.com',
|
||||
custom: false,
|
||||
},
|
||||
]
|
||||
|
@ -166,6 +171,7 @@ interface MangoStore extends State {
|
|||
}
|
||||
settings: {
|
||||
uiLocked: boolean
|
||||
nfts: string[]
|
||||
}
|
||||
tradeHistory: any[]
|
||||
set: (x: any) => void
|
||||
|
@ -180,6 +186,8 @@ const useMangoStore = create<MangoStore>((set, get) => {
|
|||
? JSON.parse(localStorage.getItem(NODE_URL_KEY)) || ENDPOINT.url
|
||||
: ENDPOINT.url
|
||||
|
||||
console.log('RPC url: ', rpcUrl)
|
||||
|
||||
const defaultMarket =
|
||||
typeof window !== 'undefined'
|
||||
? JSON.parse(localStorage.getItem(DEFAULT_MARKET_KEY)) || initialMarket
|
||||
|
@ -237,6 +245,7 @@ const useMangoStore = create<MangoStore>((set, get) => {
|
|||
wallet: INITIAL_STATE.WALLET,
|
||||
settings: {
|
||||
uiLocked: true,
|
||||
nfts: [],
|
||||
},
|
||||
tradeHistory: [],
|
||||
set: (fn) => set(produce(fn)),
|
||||
|
@ -272,6 +281,64 @@ const useMangoStore = create<MangoStore>((set, get) => {
|
|||
})
|
||||
}
|
||||
},
|
||||
async fetchProfilePicture() {
|
||||
const wallet = get().wallet.current
|
||||
const connected = get().wallet.connected
|
||||
const connection = get().connection.current
|
||||
const set = get().set
|
||||
|
||||
if (wallet?.publicKey && connected) {
|
||||
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
|
||||
wallet.publicKey,
|
||||
{ programId: TOKEN_PROGRAM_ID }
|
||||
)
|
||||
|
||||
const nftPublicKeys = []
|
||||
|
||||
for (const token of tokenAccounts.value) {
|
||||
const tokenAccount = token.account.data.parsed.info
|
||||
|
||||
if (
|
||||
parseFloat(tokenAccount.tokenAmount.amount) == 1 &&
|
||||
tokenAccount.tokenAmount.decimals == 0
|
||||
) {
|
||||
nftPublicKeys.push(new PublicKey(tokenAccount.mint))
|
||||
}
|
||||
}
|
||||
|
||||
if (nftPublicKeys.length == 0) return
|
||||
|
||||
const metadataProgramId = new PublicKey(METADATA_SCHEMA)
|
||||
const uris = []
|
||||
|
||||
for (const nft of nftPublicKeys) {
|
||||
const pda = await findProgramAddress(
|
||||
[
|
||||
Buffer.from(METADATA_PREFIX),
|
||||
metadataProgramId.toBuffer(),
|
||||
nft.toBuffer(),
|
||||
],
|
||||
metadataProgramId
|
||||
)[0]
|
||||
|
||||
const accountInfo = await connection.getAccountInfo(
|
||||
pda,
|
||||
'processed'
|
||||
)
|
||||
const metadata = borsh.deserializeUnchecked(
|
||||
METADATA_SCHEMA,
|
||||
Metadata,
|
||||
accountInfo!.data
|
||||
)
|
||||
const uri = metadata.data.uri.replace(/\0/g, '')
|
||||
uris.push(uri)
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
state.settings.nfts = uris
|
||||
})
|
||||
}
|
||||
},
|
||||
async fetchAllMangoAccounts() {
|
||||
const set = get().set
|
||||
const mangoGroup = get().selectedMangoGroup.current
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
Taken from: https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/actions/metadata.ts
|
||||
*/
|
||||
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import BN from 'bn.js'
|
||||
import { BinaryReader, BinaryWriter } from 'borsh'
|
||||
import {
|
||||
StringPublicKey,
|
||||
EDITION_MARKER_BIT_SIZE,
|
||||
MetadataKey,
|
||||
FileOrString,
|
||||
MetadataCategory,
|
||||
MetaplexKey,
|
||||
} from './types'
|
||||
import base58 from 'bs58'
|
||||
|
||||
export class MasterEditionV1 {
|
||||
key: MetadataKey
|
||||
supply: BN
|
||||
maxSupply?: BN
|
||||
/// Can be used to mint tokens that give one-time permission to mint a single limited edition.
|
||||
printingMint: StringPublicKey
|
||||
/// If you don't know how many printing tokens you are going to need, but you do know
|
||||
/// you are going to need some amount in the future, you can use a token from this mint.
|
||||
/// Coming back to token metadata with one of these tokens allows you to mint (one time)
|
||||
/// any number of printing tokens you want. This is used for instance by Auction Manager
|
||||
/// with participation NFTs, where we dont know how many people will bid and need participation
|
||||
/// printing tokens to redeem, so we give it ONE of these tokens to use after the auction is over,
|
||||
/// because when the auction begins we just dont know how many printing tokens we will need,
|
||||
/// but at the end we will. At the end it then burns this token with token-metadata to
|
||||
/// get the printing tokens it needs to give to bidders. Each bidder then redeems a printing token
|
||||
/// to get their limited editions.
|
||||
oneTimePrintingAuthorizationMint: StringPublicKey
|
||||
|
||||
constructor(args: {
|
||||
key: MetadataKey
|
||||
supply: BN
|
||||
maxSupply?: BN
|
||||
printingMint: StringPublicKey
|
||||
oneTimePrintingAuthorizationMint: StringPublicKey
|
||||
}) {
|
||||
this.key = MetadataKey.MasterEditionV1
|
||||
this.supply = args.supply
|
||||
this.maxSupply = args.maxSupply
|
||||
this.printingMint = args.printingMint
|
||||
this.oneTimePrintingAuthorizationMint =
|
||||
args.oneTimePrintingAuthorizationMint
|
||||
}
|
||||
}
|
||||
|
||||
export class MasterEditionV2 {
|
||||
key: MetadataKey
|
||||
supply: BN
|
||||
maxSupply?: BN
|
||||
|
||||
constructor(args: { key: MetadataKey; supply: BN; maxSupply?: BN }) {
|
||||
this.key = MetadataKey.MasterEditionV2
|
||||
this.supply = args.supply
|
||||
this.maxSupply = args.maxSupply
|
||||
}
|
||||
}
|
||||
|
||||
export class EditionMarker {
|
||||
key: MetadataKey
|
||||
ledger: number[]
|
||||
|
||||
constructor(args: { key: MetadataKey; ledger: number[] }) {
|
||||
this.key = MetadataKey.EditionMarker
|
||||
this.ledger = args.ledger
|
||||
}
|
||||
|
||||
editionTaken(edition: number) {
|
||||
const editionOffset = edition % EDITION_MARKER_BIT_SIZE
|
||||
const indexOffset = Math.floor(editionOffset / 8)
|
||||
|
||||
if (indexOffset > 30) {
|
||||
throw Error('bad index for edition')
|
||||
}
|
||||
|
||||
const positionInBitsetFromRight = 7 - (editionOffset % 8)
|
||||
|
||||
const mask = Math.pow(2, positionInBitsetFromRight)
|
||||
|
||||
const appliedMask = this.ledger[indexOffset] & mask
|
||||
|
||||
return appliedMask !== 0
|
||||
}
|
||||
}
|
||||
|
||||
export class Edition {
|
||||
key: MetadataKey
|
||||
/// Points at MasterEdition struct
|
||||
parent: StringPublicKey
|
||||
/// Starting at 0 for master record, this is incremented for each edition minted.
|
||||
edition: BN
|
||||
|
||||
constructor(args: {
|
||||
key: MetadataKey
|
||||
parent: StringPublicKey
|
||||
edition: BN
|
||||
}) {
|
||||
this.key = MetadataKey.EditionV1
|
||||
this.parent = args.parent
|
||||
this.edition = args.edition
|
||||
}
|
||||
}
|
||||
export class Creator {
|
||||
address: StringPublicKey
|
||||
verified: boolean
|
||||
share: number
|
||||
|
||||
constructor(args: {
|
||||
address: StringPublicKey
|
||||
verified: boolean
|
||||
share: number
|
||||
}) {
|
||||
this.address = args.address
|
||||
this.verified = args.verified
|
||||
this.share = args.share
|
||||
}
|
||||
}
|
||||
|
||||
export class Data {
|
||||
name: string
|
||||
symbol: string
|
||||
uri: string
|
||||
sellerFeeBasisPoints: number
|
||||
creators: Creator[] | null
|
||||
constructor(args: {
|
||||
name: string
|
||||
symbol: string
|
||||
uri: string
|
||||
sellerFeeBasisPoints: number
|
||||
creators: Creator[] | null
|
||||
}) {
|
||||
this.name = args.name
|
||||
this.symbol = args.symbol
|
||||
this.uri = args.uri
|
||||
this.sellerFeeBasisPoints = args.sellerFeeBasisPoints
|
||||
this.creators = args.creators
|
||||
}
|
||||
}
|
||||
|
||||
export class Metadata {
|
||||
key: MetadataKey
|
||||
updateAuthority: StringPublicKey
|
||||
mint: StringPublicKey
|
||||
data: Data
|
||||
primarySaleHappened: boolean
|
||||
isMutable: boolean
|
||||
editionNonce: number | null
|
||||
|
||||
constructor(args: {
|
||||
updateAuthority: StringPublicKey
|
||||
mint: StringPublicKey
|
||||
data: Data
|
||||
primarySaleHappened: boolean
|
||||
isMutable: boolean
|
||||
editionNonce: number | null
|
||||
}) {
|
||||
this.key = MetadataKey.MetadataV1
|
||||
this.updateAuthority = args.updateAuthority
|
||||
this.mint = args.mint
|
||||
this.data = args.data
|
||||
this.primarySaleHappened = args.primarySaleHappened
|
||||
this.isMutable = args.isMutable
|
||||
this.editionNonce = args.editionNonce
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMetadataExtension {
|
||||
name: string
|
||||
symbol: string
|
||||
|
||||
creators: Creator[] | null
|
||||
description: string
|
||||
// preview image absolute URI
|
||||
image: string
|
||||
animation_url?: string
|
||||
|
||||
// stores link to item on meta
|
||||
external_url: string
|
||||
|
||||
seller_fee_basis_points: number
|
||||
|
||||
properties: {
|
||||
files?: FileOrString[]
|
||||
category: MetadataCategory
|
||||
maxSupply?: number
|
||||
creators?: {
|
||||
address: string
|
||||
shares: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export const METADATA_SCHEMA = new Map<any, any>([
|
||||
[
|
||||
MasterEditionV1,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['supply', 'u64'],
|
||||
['maxSupply', { kind: 'option', type: 'u64' }],
|
||||
['printingMint', 'pubkeyAsString'],
|
||||
['oneTimePrintingAuthorizationMint', 'pubkeyAsString'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
MasterEditionV2,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['supply', 'u64'],
|
||||
['maxSupply', { kind: 'option', type: 'u64' }],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Edition,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['parent', 'pubkeyAsString'],
|
||||
['edition', 'u64'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Data,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['name', 'string'],
|
||||
['symbol', 'string'],
|
||||
['uri', 'string'],
|
||||
['sellerFeeBasisPoints', 'u16'],
|
||||
['creators', { kind: 'option', type: [Creator] }],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Creator,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['address', 'pubkeyAsString'],
|
||||
['verified', 'u8'],
|
||||
['share', 'u8'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Metadata,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['updateAuthority', 'pubkeyAsString'],
|
||||
['mint', 'pubkeyAsString'],
|
||||
['data', Data],
|
||||
['primarySaleHappened', 'u8'], // bool
|
||||
['isMutable', 'u8'], // bool
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
EditionMarker,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['ledger', [31]],
|
||||
],
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
export class WhitelistedCreator {
|
||||
key = MetaplexKey.WhitelistedCreatorV1
|
||||
address: StringPublicKey
|
||||
activated = true
|
||||
|
||||
// Populated from name service
|
||||
twitter?: string
|
||||
name?: string
|
||||
image?: string
|
||||
description?: string
|
||||
|
||||
constructor(args: { address: string; activated: boolean }) {
|
||||
this.address = args.address
|
||||
this.activated = args.activated
|
||||
}
|
||||
}
|
||||
|
||||
// Required to properly serialize and deserialize pubKeyAsString types
|
||||
const extendBorsh = () => {
|
||||
;(BinaryReader.prototype as any).readPubkey = function () {
|
||||
const reader = this as unknown as BinaryReader
|
||||
const array = reader.readFixedArray(32)
|
||||
return new PublicKey(array)
|
||||
}
|
||||
|
||||
;(BinaryWriter.prototype as any).writePubkey = function (value: any) {
|
||||
const writer = this as unknown as BinaryWriter
|
||||
writer.writeFixedArray(value.toBuffer())
|
||||
}
|
||||
|
||||
;(BinaryReader.prototype as any).readPubkeyAsString = function () {
|
||||
const reader = this as unknown as BinaryReader
|
||||
const array = reader.readFixedArray(32)
|
||||
return base58.encode(array) as StringPublicKey
|
||||
}
|
||||
|
||||
;(BinaryWriter.prototype as any).writePubkeyAsString = function (
|
||||
value: StringPublicKey
|
||||
) {
|
||||
const writer = this as unknown as BinaryWriter
|
||||
writer.writeFixedArray(base58.decode(value))
|
||||
}
|
||||
}
|
||||
|
||||
extendBorsh()
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Taken from: https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/actions/metadata.ts
|
||||
*/
|
||||
|
||||
export type StringPublicKey = string
|
||||
|
||||
export const EDITION = 'edition'
|
||||
export const METADATA_PREFIX = 'metadata'
|
||||
export const METADATA_KEY = 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
|
||||
|
||||
export const MAX_AUCTION_DATA_EXTENDED_SIZE = 8 + 9 + 2 + 200
|
||||
|
||||
export const MAX_NAME_LENGTH = 32
|
||||
export const MAX_SYMBOL_LENGTH = 10
|
||||
export const MAX_URI_LENGTH = 200
|
||||
export const MAX_CREATOR_LIMIT = 5
|
||||
export const EDITION_MARKER_BIT_SIZE = 248
|
||||
export const MAX_CREATOR_LEN = 32 + 1 + 1
|
||||
export const MAX_METADATA_LEN =
|
||||
1 +
|
||||
32 +
|
||||
32 +
|
||||
MAX_NAME_LENGTH +
|
||||
MAX_SYMBOL_LENGTH +
|
||||
MAX_URI_LENGTH +
|
||||
MAX_CREATOR_LIMIT * MAX_CREATOR_LEN +
|
||||
2 +
|
||||
1 +
|
||||
1 +
|
||||
198
|
||||
|
||||
export enum MetadataKey {
|
||||
Uninitialized = 0,
|
||||
MetadataV1 = 4,
|
||||
EditionV1 = 1,
|
||||
MasterEditionV1 = 2,
|
||||
MasterEditionV2 = 6,
|
||||
EditionMarker = 7,
|
||||
}
|
||||
|
||||
export enum MetadataCategory {
|
||||
Audio = 'audio',
|
||||
Video = 'video',
|
||||
Image = 'image',
|
||||
VR = 'vr',
|
||||
HTML = 'html',
|
||||
}
|
||||
|
||||
export type MetadataFile = {
|
||||
uri: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export type FileOrString = MetadataFile | string
|
||||
|
||||
export interface Auction {
|
||||
name: string
|
||||
auctionerName: string
|
||||
auctionerLink: string
|
||||
highestBid: number
|
||||
solAmt: number
|
||||
link: string
|
||||
image: string
|
||||
}
|
||||
|
||||
export interface Artist {
|
||||
address?: string
|
||||
name: string
|
||||
link: string
|
||||
image: string
|
||||
itemsAvailable?: number
|
||||
itemsSold?: number
|
||||
about?: string
|
||||
verified?: boolean
|
||||
|
||||
share?: number
|
||||
}
|
||||
|
||||
export enum ArtType {
|
||||
Master,
|
||||
Print,
|
||||
NFT,
|
||||
}
|
||||
export interface Art {
|
||||
url: string
|
||||
}
|
||||
|
||||
export enum MetaplexKey {
|
||||
Uninitialized = 0,
|
||||
OriginalAuthorityLookupV1 = 1,
|
||||
BidRedemptionTicketV1 = 2,
|
||||
StoreV1 = 3,
|
||||
WhitelistedCreatorV1 = 4,
|
||||
PayoutTicketV1 = 5,
|
||||
SafetyDepositValidationTicketV1 = 6,
|
||||
AuctionManagerV1 = 7,
|
||||
PrizeTrackingTicketV1 = 8,
|
||||
SafetyDepositConfigV1 = 9,
|
||||
AuctionManagerV2 = 10,
|
||||
BidRedemptionTicketV2 = 11,
|
||||
AuctionWinnerTokenTypeTrackerV1 = 12,
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { PublicKey } from '@solana/web3.js'
|
||||
|
||||
export const findProgramAddress = async (
|
||||
seeds: (Buffer | Uint8Array)[],
|
||||
programId: PublicKey
|
||||
) => {
|
||||
const key =
|
||||
'pda-' +
|
||||
seeds.reduce((agg, item) => agg + item.toString('hex'), '') +
|
||||
programId.toString()
|
||||
const cached = localStorage.getItem(key)
|
||||
if (cached) {
|
||||
const value = JSON.parse(cached)
|
||||
|
||||
return [new PublicKey(value.key), parseInt(value.nonce)] as [
|
||||
PublicKey,
|
||||
number
|
||||
]
|
||||
}
|
||||
|
||||
const result = await PublicKey.findProgramAddress(seeds, programId)
|
||||
|
||||
return [result[0], result[1]] as [PublicKey, number]
|
||||
}
|
Loading…
Reference in New Issue