Store metadata json in update call

This commit is contained in:
Jordan Prince 2021-04-05 14:02:45 -05:00
parent 01bbfa5303
commit f9bfa0fd36
4 changed files with 170 additions and 43 deletions

View File

@ -194,6 +194,17 @@ class CreateMetadataArgs {
this.uri = args.uri;
}
}
class UpdateMetadataArgs {
instruction: number = 1;
uri: string;
// Not used by this app, just required for instruction
non_unique_specific_update_authority: number;
constructor(args: { uri: string }) {
this.uri = args.uri;
this.non_unique_specific_update_authority = 0;
}
}
export class Metadata {
updateAuthority?: PublicKey;
@ -233,6 +244,17 @@ export const SCHEMA = new Map<any, any>([
],
},
],
[
UpdateMetadataArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['uri', 'string'],
['non_unique_specific_update_authority', 'u8'],
],
},
],
[
Metadata,
{
@ -280,6 +302,68 @@ export function createMint(
return account;
}
export async function updateMetadata(
symbol: string,
name: string,
uri: string,
mintKey: PublicKey,
updateAuthority: PublicKey,
instructions: TransactionInstruction[],
signers: Account[],
) {
const metadataProgramId = programIds().metadata;
const metadataAccount = (
await PublicKey.findProgramAddress(
[
Buffer.from('metadata'),
metadataProgramId.toBuffer(),
mintKey.toBuffer(),
],
metadataProgramId,
)
)[0];
const metadataOwnerAccount = (
await PublicKey.findProgramAddress(
[
Buffer.from('metadata'),
metadataProgramId.toBuffer(),
Buffer.from(name),
Buffer.from(symbol),
],
metadataProgramId,
)
)[0];
const value = new UpdateMetadataArgs({ uri });
const data = Buffer.from(serialize(SCHEMA, value));
const keys = [
{
pubkey: metadataAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: updateAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: metadataOwnerAccount,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: metadataProgramId,
data,
}),
);
}
export async function createMetadata(
symbol: string,
@ -289,7 +373,7 @@ export async function createMetadata(
mintAuthorityKey: PublicKey,
instructions: TransactionInstruction[],
payer: PublicKey,
owner: PublicKey,
updateAuthority: PublicKey,
signers: Account[],
) {
const metadataProgramId = programIds().metadata;
@ -347,7 +431,7 @@ export async function createMetadata(
isWritable: false,
},
{
pubkey: payer,
pubkey: updateAuthority,
isSigner: true,
isWritable: false,
},

View File

@ -1,4 +1,14 @@
import { EventEmitter, programIds, useConnection, decodeMetadata, Metadata, getMultipleAccounts, cache, MintParser, ParsedAccount } from '@oyster/common';
import {
EventEmitter,
programIds,
useConnection,
decodeMetadata,
Metadata,
getMultipleAccounts,
cache,
MintParser,
ParsedAccount,
} from '@oyster/common';
import { MintInfo } from '@solana/spl-token';
import BN from 'bn.js';
import React, { useContext, useEffect, useState } from 'react';
@ -18,15 +28,18 @@ export function VinciAccountsProvider({ children = null as any }) {
useEffect(() => {
(async () => {
const metadataAccounts = await connection.getProgramAccounts(programIds().metadata);
const metadataAccounts = await connection.getProgramAccounts(
programIds().metadata,
);
const mintToMetadata = new Map<string, Metadata>();
const extendedMetadataFetch = new Map<string, Promise<any>>();
metadataAccounts.forEach(meta => {
try{
try {
const metadata = decodeMetadata(meta.account.data);
if(isValidHttpUrl(metadata.uri)) {
if (isValidHttpUrl(metadata.uri)) {
console.log('Metadata uri', metadata.uri);
mintToMetadata.set(metadata.mint.toBase58(), metadata);
}
} catch {
@ -35,22 +48,35 @@ export function VinciAccountsProvider({ children = null as any }) {
}
});
const mints = await getMultipleAccounts(connection, [...mintToMetadata.keys()], 'single');
const mints = await getMultipleAccounts(
connection,
[...mintToMetadata.keys()],
'single',
);
mints.keys.forEach((key, index) => {
const mintAccount = mints.array[index];
const mint = cache.add(key, mintAccount, MintParser) as ParsedAccount<MintInfo>;
if(mint.info.supply.gt(new BN(1)) || mint.info.decimals !== 0) {
const mint = cache.add(
key,
mintAccount,
MintParser,
) as ParsedAccount<MintInfo>;
if (mint.info.supply.gt(new BN(1)) || mint.info.decimals !== 0) {
// naive not NFT check
mintToMetadata.delete(key);
} else {
const metadata = mintToMetadata.get(key);
if(metadata && metadata.uri) {
extendedMetadataFetch.set(key, fetch(metadata.uri).catch(() => {
mintToMetadata.delete(key);
return undefined;
}).then(_ => {
metadata.extended = _;
}));
if (metadata && metadata.uri) {
extendedMetadataFetch.set(
key,
fetch(metadata.uri)
.catch(() => {
mintToMetadata.delete(key);
return undefined;
})
.then(_ => {
metadata.extended = _;
}),
);
}
}
});
@ -61,7 +87,7 @@ export function VinciAccountsProvider({ children = null as any }) {
console.log([...mintToMetadata.values()]);
})();
}, [connection, setMetaAccounts])
}, [connection, setMetaAccounts]);
return (
<VinciAccountsContext.Provider value={{ metaAccounts }}>
@ -84,5 +110,5 @@ function isValidHttpUrl(text: string) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
return url.protocol === 'http:' || url.protocol === 'https:';
}

View File

@ -2,8 +2,10 @@ import {
createAssociatedTokenAccountInstruction,
createMint,
createMetadata,
updateMetadata,
programIds,
sendTransactions,
sendTransaction,
} from '@oyster/common';
import { MintLayout, Token } from '@solana/spl-token';
import { WalletAdapter } from '@solana/wallet-base';
@ -12,12 +14,12 @@ import {
Connection,
PublicKey,
SystemProgram,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import crypto from 'crypto';
import { getAssetCostToStore } from '../utils/assets';
import { AR_SOL_HOLDER_ID } from '../utils/ids';
import { IMetadata } from '../views/artCreate';
interface IArweaveResult {
error?: string;
@ -32,7 +34,7 @@ export const mintNFT = async (
connection: Connection,
wallet: WalletAdapter | undefined,
files: File[],
metadata: any,
metadata: { name: string; symbol: string },
): Promise<IArweaveResult> => {
if (!wallet?.publicKey) {
return { error: 'No wallet' };
@ -99,8 +101,8 @@ export const mintNFT = async (
);
await createMetadata(
`🥭🧢#`,
`name: jjjdsfsk🥭🧢#`,
metadata.symbol,
metadata.name,
`https://google.com`,
mintKey,
owner.publicKey,
@ -109,18 +111,6 @@ export const mintNFT = async (
wallet.publicKey,
signers,
);
// For Jordan -> Transfer SOL
console.log(files.length);
// TODO:
// instructions.push(
// Token.createSetAuthorityInstruction(
// TOKEN_PROGRAM_ID,
// mintKey,
// owner.publicKey,
// owner.publicKey,
// []));
return new Promise(async res => {
const txId = await sendTransactions(
connection,
@ -157,6 +147,33 @@ export const mintNFT = async (
},
)
).json();
const metadataFile = result.messages?.find(
m => m.filename == 'metadata.json',
);
if (metadataFile?.transactionId && wallet.publicKey) {
const updateInstructions: TransactionInstruction[] = [];
const updateSigners: Account[] = [];
await updateMetadata(
metadata.symbol,
metadata.name,
`https://arweave.net/${metadataFile.transactionId}`,
mintKey,
wallet.publicKey,
updateInstructions,
updateSigners,
);
await sendTransaction(
connection,
wallet,
updateInstructions,
updateSigners,
true,
'singleGossip',
);
}
console.log('Result', result);
res(result);
}

View File

@ -27,7 +27,7 @@ enum Category {
Video = 'video',
Image = 'image',
}
interface IProps {
export interface IMetadata {
name: string;
symbol: string;
description: string;
@ -45,7 +45,7 @@ export const ArtCreateView = () => {
const { wallet, connected } = useWallet();
const [step, setStep] = useState(0);
const [saving, setSaving] = useState(false);
const [attributes, setAttributes] = useState<IProps>({
const [attributes, setAttributes] = useState<IMetadata>({
name: '',
symbol: '',
description: '',
@ -178,8 +178,8 @@ const CategoryStep = (props: { confirm: (category: Category) => void }) => {
};
const UploadStep = (props: {
attributes: IProps;
setAttributes: (attr: IProps) => void;
attributes: IMetadata;
setAttributes: (attr: IMetadata) => void;
confirm: () => void;
}) => {
return (
@ -232,8 +232,8 @@ const UploadStep = (props: {
};
const InfoStep = (props: {
attributes: IProps;
setAttributes: (attr: IProps) => void;
attributes: IMetadata;
setAttributes: (attr: IMetadata) => void;
confirm: () => void;
}) => {
const file = props.attributes.files[0];
@ -319,8 +319,8 @@ const InfoStep = (props: {
};
const RoyaltiesStep = (props: {
attributes: IProps;
setAttributes: (attr: IProps) => void;
attributes: IMetadata;
setAttributes: (attr: IMetadata) => void;
confirm: () => void;
}) => {
const file = props.attributes.files[0];
@ -371,7 +371,7 @@ const RoyaltiesStep = (props: {
);
};
const LaunchStep = (props: { confirm: () => void; attributes: IProps }) => {
const LaunchStep = (props: { confirm: () => void; attributes: IMetadata }) => {
const file = props.attributes.files[0];
const metadata = {
...(props.attributes as any),