Skip to content

Commit

Permalink
uopdate
Browse files Browse the repository at this point in the history
  • Loading branch information
christian morales authored and christian morales committed Sep 21, 2024
1 parent d5bdcbd commit 3ecc7d7
Show file tree
Hide file tree
Showing 11 changed files with 4,015 additions and 540 deletions.
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,29 @@
"dependencies": {
"@headlessui/react": "^2.1.8",
"@heroicons/react": "^2.1.5",
"@metaplex-foundation/mpl-token-metadata": "^3.2.1",
"@rainbow-me/rainbowkit": "^2.1.6",
"@solana/spl-token": "^0.4.8",
"@solana/wallet-adapter-base": "^0.9.23",
"@solana/wallet-adapter-react": "^0.15.35",
"@solana/wallet-adapter-react-ui": "^0.9.35",
"@solana/wallet-adapter-wallets": "^0.19.32",
"@solana/web3.js": "^1.95.3",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.9",
"@tanstack/react-query": "^5.56.2",
"@vercel/speed-insights": "^1.0.12",
"@web3forms/react": "^1.1.3",
"bs58": "^6.0.0",
"encoding": "^0.1.13",
"express-rate-limit": "^7.4.0",
"lucide-react": "^0.441.0",
"next": "14.2.12",
"next": "^14.2.13",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.53.0",
"tweetnacl": "^1.0.3",
"viem": "2.x",
"wagmi": "^2.12.12"
},
Expand Down
Binary file added public/images/solana-hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
244 changes: 244 additions & 0 deletions src/app/api/solana/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import { NextApiRequest, NextApiResponse } from "next";
import {
Connection,
PublicKey,
Transaction,
SystemProgram,
LAMPORTS_PER_SOL,
} from "@solana/web3.js";
import {
createMint,
getOrCreateAssociatedTokenAccount,
mintTo,
createSetAuthorityInstruction,
AuthorityType,
} from "@solana/spl-token";
import { Metadata } from "@metaplex-foundation/mpl-token-metadata";
import * as bs58 from "bs58";
import rateLimit from "express-rate-limit";
import nacl from "tweetnacl";

const SOLANA_RPC_URL =
process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
const SERVICE_FEE_WALLET = new PublicKey(process.env.SERVICE_FEE_WALLET!);
const SERVICE_FEE = 0.1 * LAMPORTS_PER_SOL; // 0.1 SOL in lamports

const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
});

interface TokenCreationRequest {
name: string;
symbol: string;
decimals: number;
supply: number;
uri?: string;
immutable: boolean;
revokeMint: boolean;
revokeFreeze: boolean;
walletPublicKey: string;
signature: string;
}

function validateInput(body): TokenCreationRequest {
if (!body.name || typeof body.name !== "string" || body.name.length > 32)
throw new Error("Invalid name");
if (
!body.symbol ||
typeof body.symbol !== "string" ||
body.symbol.length > 10
)
throw new Error("Invalid symbol");
if (
!body.decimals ||
typeof body.decimals !== "number" ||
body.decimals < 0 ||
body.decimals > 9
)
throw new Error("Invalid decimals");
if (!body.supply || typeof body.supply !== "number" || body.supply <= 0)
throw new Error("Invalid supply");
if (!body.walletPublicKey || typeof body.walletPublicKey !== "string")
throw new Error("Invalid wallet public key");
if (!body.signature || typeof body.signature !== "string")
throw new Error("Invalid signature");
if (body.uri && typeof body.uri !== "string") throw new Error("Invalid URI");
if (typeof body.immutable !== "boolean")
throw new Error("Invalid immutable flag");
if (typeof body.revokeMint !== "boolean")
throw new Error("Invalid revokeMint flag");
if (typeof body.revokeFreeze !== "boolean")
throw new Error("Invalid revokeFreeze flag");

return body as TokenCreationRequest;
}

async function verifySignature(
message: string,
signature: string,
publicKey: string
): Promise<boolean> {
return nacl.sign.detached.verify(
new TextEncoder().encode(message),
bs58.decode(signature),
bs58.decode(publicKey)
);
}

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}

await limiter(req, res);

try {
const validatedInput = validateInput(req.body);
const {
name,
symbol,
decimals,
supply,
uri,
immutable,
revokeMint,
revokeFreeze,
walletPublicKey,
signature,
} = validatedInput;

// Verify the signature
const message = `Create token: ${name} (${symbol}) with 0.1 SOL fee`;
const isValidSignature = await verifySignature(
message,
signature,
walletPublicKey
);
if (!isValidSignature) {
return res.status(401).json({ error: "Invalid signature" });
}

const connection = new Connection(SOLANA_RPC_URL, "confirmed");
const userPublicKey = new PublicKey(walletPublicKey);

// Create a new transaction
const transaction = new Transaction();

// Add instruction to transfer 0.1 SOL to the service fee wallet
transaction.add(
SystemProgram.transfer({
fromPubkey: userPublicKey,
toPubkey: SERVICE_FEE_WALLET,
lamports: SERVICE_FEE,
})
);

// Create the token mint
const mint = await createMint(
connection,
{ publicKey: userPublicKey, secretKey: new Uint8Array(0) },
userPublicKey,
userPublicKey,
decimals
);

// Get or create associated token account
const fromTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
{ publicKey: userPublicKey, secretKey: new Uint8Array(0) },
mint,
userPublicKey
);

// Add instruction to mint tokens
transaction.add(
mintTo(mint, fromTokenAccount.address, userPublicKey, supply)
);

// Handle additional settings
if (immutable) {
transaction.add(
createSetAuthorityInstruction(
mint,
userPublicKey,
AuthorityType.MintTokens,
null
)
);
}

if (revokeMint) {
transaction.add(
createSetAuthorityInstruction(
mint,
userPublicKey,
AuthorityType.MintTokens,
null
)
);
}

if (revokeFreeze) {
transaction.add(
createSetAuthorityInstruction(
mint,
userPublicKey,
AuthorityType.FreezeAccount,
null
)
);
}

// Create metadata
const metadataAccount = await Metadata.getPDA(mint);
const metadataInstruction = await Metadata.createInstruction(
{
metadata: metadataAccount,
mint,
mintAuthority: userPublicKey,
payer: userPublicKey,
updateAuthority: userPublicKey,
},
{
createMetadataAccountArgsV2: {
data: {
name,
symbol,
uri: uri || "",
sellerFeeBasisPoints: 0,
creators: null,
collection: null,
uses: null,
},
isMutable: true,
},
}
);

transaction.add(metadataInstruction);

// Serialize the transaction
const serializedTransaction = transaction.serialize({
requireAllSignatures: false,
verifySignatures: false,
});

return res.status(200).json({
success: true,
tokenAddress: mint.toBase58(),
tokenAccount: fromTokenAccount.address.toBase58(),
metadataAccount: metadataAccount.toBase58(),
transaction: bs58.encode(serializedTransaction),
fee: SERVICE_FEE / LAMPORTS_PER_SOL,
});
} catch (error) {
console.error("Error creating token:", error);
return res
.status(500)
.json({ error: "Error creating token", details: error.message });
}
}
55 changes: 49 additions & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,69 @@
"use client";
import Steps from "../components/steps";
import Faq from "../components/faq";
import Collections from "../components/collections";
import Image from "next/image";

export default function Home() {
return (
<div>
<main>
<section className="bg-white dark:bg-gray-900">
<div className="grid max-w-screen-xl px-4 py-8 mx-auto lg:gap-8 xl:gap-0 lg:py-16 lg:grid-cols-12">
<div className="mr-auto place-self-center lg:col-span-7">
<h1 className="max-w-2xl mb-4 text-4xl font-extrabold tracking-tight leading-none md:text-5xl xl:text-6xl dark:text-white">
Create your crypto token with no code
</h1>
<p className="max-w-2xl mb-6 font-light text-gray-500 lg:mb-8 md:text-lg lg:text-xl dark:text-gray-400">
From concept to launch, creators and entrepreneurs worldwide use
our platform to effortlessly create and deploy custom crypto
tokens without any coding knowledge.
</p>
<a
href="#"
className="inline-flex items-center justify-center px-5 py-3 mr-3 text-base font-medium text-center rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 text-gray-800 dark:text-white dark:focus:ring-primary-900"
>
Get started
<svg
className="w-5 h-5 ml-2 -mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</a>
<a
href="/solana"
className="inline-flex items-center justify-center px-5 py-3 text-base font-medium text-center text-gray-900 border border-gray-300 rounded-lg hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 dark:text-white dark:border-gray-700 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
>
Create Solana Token
</a>
</div>
<div className="hidden lg:mt-0 lg:col-span-5 lg:flex">
<Image
src="/images/solana-hero.png"
alt="mockup"
width={500}
height={300}
/>
</div>
</div>
</section>
<div className="relative">
<div className="relative mx-auto max-w-3xl px-4 pb-96 text-center sm:px-6 sm:pb-0 lg:px-8">
<div className="relative pb-10 pt-8">
<h1 className="text-2xl sm:text-6xl md:text-5xl font-extrabold bg-clip-text text-gray-900 dark:text-white">
Choose your blockchain
</h1>
<p className="mt-4 text-xl sm:text-2xl md:text-3xl text-gray-600 dark:text-gray-300">
Then create your coin
Supported Networks
</p>
</div>
</div>

<Collections />
<Steps />
{/* <Steps /> */}
<Faq />
</div>
</main>
Expand Down
Loading

0 comments on commit 3ecc7d7

Please sign in to comment.