Skip to content

Commit

Permalink
create token & fetch metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
tkorkmazeth committed Oct 3, 2024
1 parent 853c9dc commit 79505b3
Show file tree
Hide file tree
Showing 10 changed files with 474 additions and 193 deletions.
245 changes: 106 additions & 139 deletions src/components/CreateToken.tsx
Original file line number Diff line number Diff line change
@@ -1,159 +1,119 @@
import { FC, useCallback, useState } from "react";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import {
createGenericFile,
createNoopSigner,
publicKey as umiPublicKey,
signerIdentity,
Keypair,
PublicKey,
SystemProgram,
Transaction,
generateSigner,
percentAmount,
transactionBuilder,
} from "@metaplex-foundation/umi";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { irysUploader } from "@metaplex-foundation/umi-uploader-irys";
} from "@solana/web3.js";
import {
MINT_SIZE,
TOKEN_PROGRAM_ID,
createInitializeMintInstruction,
getMinimumBalanceForRentExemptMint,
getAssociatedTokenAddress,
createAssociatedTokenAccountInstruction,
createMintToInstruction,
} from "@solana/spl-token";
import {
DataV2,
CreateMetadataAccountV3InstructionArgs,
createFungible,
CreateMetadataAccountV3InstructionData,
createMetadataAccountV3,
mplTokenMetadata,
} from "@metaplex-foundation/mpl-token-metadata";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { mplToolbox } from "@metaplex-foundation/mpl-toolbox";

import {
createTokenIfMissing,
findAssociatedTokenPda,
getSplAssociatedTokenProgramId,
mintTokensTo,
mplToolbox,
} from "@metaplex-foundation/mpl-toolbox";
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
import { publicKey as umiPublicKey } from "@metaplex-foundation/umi";
import { walletAdapterIdentity } from "@metaplex-foundation/umi-signer-wallet-adapters";
import { toWeb3JsLegacyTransaction } from "@metaplex-foundation/umi-web3js-adapters";
import useConnectionStore from "stores/useConnectionStore";

const umi = createUmi("https://api.devnet.solana.com")
.use(mplToolbox())
.use(mplTokenMetadata());

export const CreateToken: FC = () => {
const { connection } = useConnection();
const { publicKey, sendTransaction } = useWallet();
const { recentBlockhash } = useConnectionStore();
const wallet = useWallet();
const { sendTransaction } = useWallet();
const [tokenName, setTokenName] = useState("");
const [symbol, setSymbol] = useState("");
const [metadata, setMetadata] = useState("");
const [amount, setAmount] = useState("");
const [decimals, setDecimals] = useState("");
const [imageFile, setImageFile] = useState<File | null>(null);
const [transactionStatus, setTransactionStatus] = useState<string | null>(
null
);

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
setImageFile(event.target.files[0]);
}
};

const onClick = useCallback(async () => {
if (!wallet.publicKey || !imageFile) {
console.error("Wallet not connected or no image uploaded!");
return;
}

const signer = createNoopSigner(umiPublicKey(wallet.publicKey));

const umi = createUmi("https://api.devnet.solana.com")
.use(irysUploader())
.use(walletAdapterIdentity(wallet))
.use(mplTokenMetadata())
.use(mplToolbox());

const reader = new FileReader();
reader.onloadend = async () => {
const umiImageFile = createGenericFile(
reader.result as string,
imageFile.name,
{
tags: [{ name: "Content-Type", value: imageFile.type }],
}
const onClick = useCallback(
async (form) => {
const lamports = await getMinimumBalanceForRentExemptMint(connection);
const mintKeypair = Keypair.generate();
const tokenATA = await getAssociatedTokenAddress(
mintKeypair.publicKey,
publicKey
);

const uploadPrice = await umi.uploader.getUploadPrice([umiImageFile]);

const imageUri = await umi.uploader
.upload([umiImageFile])
.catch((err) => {
console.error("Error uploading image:", err);
throw new Error(err);
});

const metadata: DataV2 = {
name: tokenName,
symbol: symbol,
uri: imageUri[0],
sellerFeeBasisPoints: 0, // Adjust seller fee basis points as needed
creators: null,
collection: null,
uses: null,
};

console.log(
"Uploading image to Arweave via Irys",
"uploadPrice =>",
Number(uploadPrice.basisPoints) / LAMPORTS_PER_SOL,
uploadPrice.identifier
const umiWalletAdapter = umi.use(walletAdapterIdentity(wallet));

const createMetadataInstruction = createMetadataAccountV3(umi, {
mint: umiPublicKey(mintKeypair.publicKey),
mintAuthority: umiWalletAdapter.identity,
payer: umiWalletAdapter.identity,
updateAuthority: umiWalletAdapter.identity,
data: {
name: form.tokenName,
symbol: form.symbol,
uri: form.metadata,
creators: null,
sellerFeeBasisPoints: 0,
uses: null,
collection: null,
},
isMutable: false,
collectionDetails: null,
})
.setBlockhash(recentBlockhash)
.build(umi);

const web3JsTransaction = toWeb3JsLegacyTransaction(
createMetadataInstruction
);

console.log("Uploading metadata to Arweave via Irys");
const metadataUri = await umi.uploader
.uploadJson(metadata)
.catch((err) => {
console.error("Error uploading metadata:", err);
throw new Error(err);
});

const mintSigner = generateSigner(umi);

// Create the create fungible instruction
const createFungibleIx = createFungible(umi, {
mint: mintSigner,
name: tokenName,
symbol: symbol,
uri: metadataUri,
sellerFeeBasisPoints: percentAmount(0),
decimals: Number(decimals),
});

// Create the associated token account instruction
const createTokenIx = createTokenIfMissing(umi, {
mint: mintSigner.publicKey,
owner: umi.identity.publicKey,
ataProgram: getSplAssociatedTokenProgramId(umi),
});

// Mint tokens to the associated token account
const mintTokensIx = mintTokensTo(umi, {
mint: mintSigner.publicKey,
token: findAssociatedTokenPda(umi, {
mint: mintSigner.publicKey,
owner: umi.identity.publicKey,
const createNewTokenTransaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: publicKey,
newAccountPubkey: mintKeypair.publicKey,
space: MINT_SIZE,
lamports: lamports,
programId: TOKEN_PROGRAM_ID,
}),
amount: BigInt(amount),
});

const tx = await createFungibleIx
.add(createTokenIx)
.add(mintTokensIx)
.sendAndConfirm(umi);

setTransactionStatus("Transaction successful!");
};
createInitializeMintInstruction(
mintKeypair.publicKey,
form.decimals,
publicKey,
publicKey,
TOKEN_PROGRAM_ID
),
createAssociatedTokenAccountInstruction(
publicKey,
tokenATA,
publicKey,
mintKeypair.publicKey
),
createMintToInstruction(
mintKeypair.publicKey,
tokenATA,
publicKey,
form.amount * Math.pow(10, form.decimals)
),
web3JsTransaction
);

reader.readAsArrayBuffer(imageFile);
}, [
wallet.publicKey,
connection,
wallet.sendTransaction,
tokenName,
symbol,
amount,
decimals,
imageFile,
]);
createNewTokenTransaction.feePayer = publicKey;
await sendTransaction(createNewTokenTransaction, connection, {
signers: [mintKeypair],
});
},
[publicKey, connection, sendTransaction]
);

return (
<div className="my-6">
Expand All @@ -170,9 +130,10 @@ export const CreateToken: FC = () => {
onChange={(e) => setSymbol(e.target.value)}
/>
<input
type="file"
className="form-control block mb-2 w-full"
onChange={handleFileChange}
type="text"
className="form-control block mb-2 w-full px-4 py-2 text-xl font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none"
placeholder="Metadata Url"
onChange={(e) => setMetadata(e.target.value)}
/>
<input
type="number"
Expand All @@ -189,12 +150,18 @@ export const CreateToken: FC = () => {

<button
className="px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500 ..."
onClick={onClick}
onClick={() =>
onClick({
decimals: Number(decimals),
amount: Number(amount),
metadata: metadata,
symbol: symbol,
tokenName: tokenName,
})
}
>
<span>Create Token</span>
</button>

{transactionStatus && <p>{transactionStatus}</p>}
</div>
);
};
Loading

0 comments on commit 79505b3

Please sign in to comment.