diff --git a/minter/next.config.js b/minter/next.config.js index e69d0e3d..ea41d738 100644 --- a/minter/next.config.js +++ b/minter/next.config.js @@ -1,5 +1,12 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} - -module.exports = nextConfig +module.exports = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'arweave.net', + port: '', + }, + ], + },} diff --git a/minter/package.json b/minter/package.json index 49144714..d2c180dc 100644 --- a/minter/package.json +++ b/minter/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@hookform/resolvers": "^3.3.2", - "@mintbase-js/react": "0.5.4-beta.0", - "@mintbase-js/sdk": "0.5.4-beta.0", - "@mintbase-js/storage": "0.5.4-beta.0", + "@mintbase-js/react":"0.5.5-beta.2", + "@mintbase-js/sdk": "0.5.5-beta.2", + "@mintbase-js/storage": "0.5.5-beta.2", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", diff --git a/minter/pnpm-lock.yaml b/minter/pnpm-lock.yaml index 827f9fce..ec12c35c 100644 --- a/minter/pnpm-lock.yaml +++ b/minter/pnpm-lock.yaml @@ -9,14 +9,14 @@ dependencies: specifier: ^3.3.2 version: 3.3.2(react-hook-form@7.48.2) '@mintbase-js/react': - specifier: 0.5.4-beta.0 - version: 0.5.4-beta.0(borsh@0.7.0)(near-api-js@2.1.4) + specifier: 0.5.5-beta.2 + version: 0.5.5-beta.2(borsh@0.7.0)(near-api-js@2.1.4) '@mintbase-js/sdk': - specifier: 0.5.4-beta.0 - version: 0.5.4-beta.0 + specifier: 0.5.5-beta.2 + version: 0.5.5-beta.2 '@mintbase-js/storage': - specifier: 0.5.4-beta.0 - version: 0.5.4-beta.0 + specifier: 0.5.5-beta.2 + version: 0.5.5-beta.2 '@radix-ui/react-label': specifier: ^2.0.2 version: 2.0.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) @@ -224,10 +224,10 @@ packages: query-string: 7.1.3 dev: false - /@mintbase-js/react@0.5.4-beta.0(borsh@0.7.0)(near-api-js@2.1.4): - resolution: {integrity: sha512-vr4njHyMXxaCYtQnpGOfG80J8sz2f5KegJlV7qOiSUcFYETJ/4x/y0Vt5+Gf3y6vOkfEbFGd3OdS2j5uSxYBgA==} + /@mintbase-js/react@0.5.5-beta.2(borsh@0.7.0)(near-api-js@2.1.4): + resolution: {integrity: sha512-A4QIaMmtTvY9WLruoYkhXWnRNEjf4+NL3b2KNZSLoCyK/lx5q7vhfFqdCIiGhfXgnnf0AnAzqLPIoWYg6nqUAw==} dependencies: - '@mintbase-js/wallet': 0.5.2-beta.0 + '@mintbase-js/wallet': 0.5.5-fix-success-url-issue-765a756.0 '@near-wallet-selector/core': 8.9.3(near-api-js@2.1.4) '@near-wallet-selector/here-wallet': 8.9.3(borsh@0.7.0)(near-api-js@2.1.4) '@near-wallet-selector/meteor-wallet': 8.9.3(near-api-js@2.1.4) @@ -250,8 +250,8 @@ packages: - encoding dev: false - /@mintbase-js/sdk@0.5.4-beta.0: - resolution: {integrity: sha512-pFBdqYUb9BwmR4nJj2eTx/2NGMsCx+okiwT2GfitJYwUNe2ZCVwuUZOU3izdvLEUsQVrQUfDDVPSY/EOf8q4Hw==} + /@mintbase-js/sdk@0.5.5-beta.2: + resolution: {integrity: sha512-gvbRFyt4+hyPowVRP9U45/ZR8x4ti+mX9UTkXoJt6Zz7fFSxAz7cWCVambx+9bMxeE2tycz0L12aqSjxS77M4Q==} dependencies: bn.js: 5.2.1 near-api-js: 2.1.4 @@ -259,8 +259,8 @@ packages: - encoding dev: false - /@mintbase-js/storage@0.5.4-beta.0: - resolution: {integrity: sha512-eeoXcoFA/QvxHPn/OV66hPjOTsr93WaZeIpht0SsbgyMrPKqGlXvQpmtKpI1SnH1bzq4wcgo78pdgeqQl5NlQw==} + /@mintbase-js/storage@0.5.5-beta.2: + resolution: {integrity: sha512-cItytfg7pPsa54vl/rPamSp2BYjRcRgS5FLtz5Tv40p/ahEzYqlgraB6hzCpKo3OU8rvgMDiTyizPwTn22cVnw==} dependencies: '@mintbase-js/sdk': 0.5.2-beta.0 near-api-js: 2.1.4 @@ -268,8 +268,8 @@ packages: - encoding dev: false - /@mintbase-js/wallet@0.5.2-beta.0: - resolution: {integrity: sha512-8byHdddYMTn/QbrTV9Nhk7HocDSPxuye30W3e3TitgR8PvWXiY/mnj0QstiV9Cb+82dssH5vSrNQ1cUPet7xPw==} + /@mintbase-js/wallet@0.5.5-fix-success-url-issue-765a756.0: + resolution: {integrity: sha512-ooojRMJRUExlrayx1Z+0G9ZIf/6gA99nVDn5QQATsQZdnPsyLMAiBjjM/hsTJRUzMAU3JmobeEyJ2SKtIOmb6w==} dependencies: '@near-wallet-selector/core': 8.9.3(near-api-js@2.1.4) bn.js: 5.2.1 diff --git a/minter/public/favicon.ico b/minter/public/favicon.ico new file mode 100644 index 00000000..fc3cf89a Binary files /dev/null and b/minter/public/favicon.ico differ diff --git a/minter/src/app/favicon.ico b/minter/src/app/favicon.ico index 718d6fea..fc3cf89a 100644 Binary files a/minter/src/app/favicon.ico and b/minter/src/app/favicon.ico differ diff --git a/minter/src/app/layout.tsx b/minter/src/app/layout.tsx index dbfa84e6..8e72800b 100644 --- a/minter/src/app/layout.tsx +++ b/minter/src/app/layout.tsx @@ -1,29 +1,65 @@ -"use client"; - -import { Inter } from "next/font/google"; +import { AppProvider } from "@/components/Provider"; +import { Metadata } from "next"; +import { headers } from "next/headers"; import "./globals.css"; -import { MintbaseWalletContextProvider } from "@mintbase-js/react"; -import "@near-wallet-selector/modal-ui/styles.css"; -import { MintbaseWalletSetup } from "@/config/setup"; -const inter = Inter({ subsets: ["latin"] }); +const extractSignMeta = (url: string): string | null => { + const signMetaIndex = url.indexOf("signMeta="); + if (signMetaIndex === -1) { + return null; // signMeta not found + } + + const startIndex = signMetaIndex + "signMeta=".length; + const endIndex = url.indexOf("&", startIndex); + if (endIndex === -1) { + return url.substring(startIndex); // signMeta is the last parameter in the URL + } else { + return url.substring(startIndex, endIndex); + } +}; + +export async function generateMetadata(): Promise { + + const headersList = headers(); + const referer = headersList.get("referer"); + + let pageTitle = "Mintbase Minter Example"; + let pageDescription = "Learn how to Mint NFTs on NEAR with Mintbase Minter Example" + + // Check if signMeta exists in the URL + const signMeta = referer ? extractSignMeta(referer) : ""; + if (signMeta) { + const signMetaData = JSON.parse(decodeURIComponent(signMeta)); + + pageTitle = `Success! You just minted: ${signMetaData?.args?.title}`; + pageDescription = `Just Minted ${signMetaData?.args?.title} on Mintbase` + // Now you can further process the extracted signMeta value + } + + return { + metadataBase: new URL("https://minter.mintbase.xyz"), + title: pageTitle, + openGraph: { + title:pageTitle, + description: pageDescription, + images:['https://i.imgur.com/QDJPsAA.png'], + }, + twitter: { + title: pageTitle, + description: pageDescription, + siteId: "1467726470533754880", + creator: "Mintbase", + card: "summary_large_image", + images: 'https://i.imgur.com/QDJPsAA.png' + }, + }; +} export default function RootLayout({ children, }: { children: React.ReactNode; }) { - - return ( - - - -
- {children} -
- - -
- ); + return {children} ; } diff --git a/minter/src/app/page.tsx b/minter/src/app/page.tsx index a6894ca6..b74d081d 100644 --- a/minter/src/app/page.tsx +++ b/minter/src/app/page.tsx @@ -5,10 +5,37 @@ import { NearWalletConnector } from "@/components/NearWalletSelector"; import Head from "next/head"; import Minter from "@/components/Minter"; +import { useSearchParams } from "next/navigation"; +import { SuccessPage } from "@/components/Success"; +import { mbUrl, nearblocksUrl } from "@/config/setup"; export default function Home() { const { isConnected } = useMbWallet(); + const params = useSearchParams(); + + const mintedParams = params.get("signMeta") + ? JSON.parse(params.get("signMeta") as string) + : ""; + const txnHashes = params.get("transactionHashes") + ? params.get("transactionHashes") + : ""; + + + if (mintedParams) { + const metaPage = `https://${mbUrl}/ref/${mintedParams.args.ref}?type=meta`; + const txnHashUrl = `https://${nearblocksUrl}/txns/${txnHashes}`; + + const successPageData = { + nftTitle: mintedParams.args.title as string, + mediaUrl: mintedParams.args.mediaUrl as string, + metaPage, + txnHashUrl, + }; + + return ; + } + if (isConnected) return (
diff --git a/minter/src/components/Provider.tsx b/minter/src/components/Provider.tsx new file mode 100644 index 00000000..66f74157 --- /dev/null +++ b/minter/src/components/Provider.tsx @@ -0,0 +1,29 @@ +"use client" + +import { Inter } from "next/font/google"; +import { MintbaseWalletContextProvider } from "@mintbase-js/react"; +import "@near-wallet-selector/modal-ui/styles.css"; +import { MintbaseWalletSetup } from "@/config/setup"; + + +const inter = Inter({ subsets: ["latin"] }); + + +export const AppProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + + return( + + + +
+ {children} +
+ + +
+ ) +} \ No newline at end of file diff --git a/minter/src/components/Success.tsx b/minter/src/components/Success.tsx new file mode 100644 index 00000000..e5a3a3d7 --- /dev/null +++ b/minter/src/components/Success.tsx @@ -0,0 +1,45 @@ +import * as React from "react"; + +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import Link from "next/link"; + +interface SuccessPageData { + nftTitle: string; + mediaUrl: string; + metaPage: string; + txnHashUrl: string; +} + +export function SuccessPage({ data }: { data: SuccessPageData }): JSX.Element { + const { nftTitle, mediaUrl, metaPage, txnHashUrl } = data; + + return ( + + + Success you just Minted! + {nftTitle} + + +
+ {nftTitle} +
+
+ + + View Transaction + + + + + +
+ ); +} diff --git a/minter/src/config/setup.ts b/minter/src/config/setup.ts index a60429f6..f622812c 100644 --- a/minter/src/config/setup.ts +++ b/minter/src/config/setup.ts @@ -1,11 +1,24 @@ - -export const proxyAddress = process?.env?.NEXT_PUBLIC_PROXY_CONTRACT_ADDRESS || "0.minsta.proxy.mintbase.testnet"; -const contractAddress = process?.env?.NEXT_PUBLIC_MINT_CONTRACT_ADDRESS || "aiminter.mintspace2.testnet"; +export const proxyAddress = + process?.env?.NEXT_PUBLIC_PROXY_CONTRACT_ADDRESS || + "0.minsta.proxy.mintbase.testnet"; +const contractAddress = + process?.env?.NEXT_PUBLIC_MINT_CONTRACT_ADDRESS || + "aiminter.mintspace2.testnet"; const network = process?.env?.NEXT_PUBLIC_NETWORK || "testnet"; -const callbackUrl = network === "testnet" ? `https://testnet.mintbase.xyz/contract/${contractAddress}/nfts/all/0` : `https://mintbase.xyz/contract/${contractAddress}/nfts/all/0`; + +const isTestnet = network === "testnet"; +const callbackUrl = !isTestnet + ? `https://mintbase.xyz/contract/${contractAddress}/nfts/all/0` + : `https://testnet.mintbase.xyz/contract/${contractAddress}/nfts/all/0`; +export const mbUrl = !isTestnet + ? "https://www.mintbase.xyz" + : "https://testnet.mintbase.xyz"; +export const nearblocksUrl = !isTestnet + ? "https://nearblocks.io" + : "https://testnet.nearblocks.io"; export const MintbaseWalletSetup = { contractAddress, network, - callbackUrl -}; \ No newline at end of file + callbackUrl, +}; diff --git a/minter/src/hooks/useMint.ts b/minter/src/hooks/useMint.ts index 217b0115..43b0a487 100644 --- a/minter/src/hooks/useMint.ts +++ b/minter/src/hooks/useMint.ts @@ -7,7 +7,7 @@ * The hook returns an object containing the form handlers and a preview state for the image to be minted. */ -"use client" +"use client"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -16,10 +16,15 @@ import { useMbWallet } from "@mintbase-js/react"; import { zodResolver } from "@hookform/resolvers/zod"; -import { ArweaveResponse, uploadFile, uploadReference } from "@mintbase-js/storage" +import { + ArweaveResponse, + uploadFile, + uploadReference, +} from "@mintbase-js/storage"; import { formSchema } from "./formSchema"; import { MintbaseWalletSetup, proxyAddress } from "@/config/setup"; -import { Wallet } from "@near-wallet-selector/core" +import { Wallet } from "@near-wallet-selector/core"; +import { cbUrl } from "./utils"; interface SubmitData { title: string; @@ -27,7 +32,6 @@ interface SubmitData { media: ((false | File) & (false | File | undefined)) | null; } - const useMintImage = () => { const { selector, activeAccountId } = useMbWallet(); const [preview, setPreview] = useState(""); @@ -41,43 +45,64 @@ const useMintImage = () => { } }; - const onSubmit = async (data: SubmitData) => { const wallet = await getWallet(); const reference = await uploadReference({ - title: typeof data?.title === 'string' ? data.title : '', - media: data?.media as unknown as File - }) + title: typeof data?.title === "string" ? data.title : "", + media: data?.media as unknown as File, + }); - const file = uploadFile(data?.media as unknown as File - ); + console.log(reference, "reference"); - await handleMint(reference.id, file, activeAccountId as string, wallet); + const file = uploadFile(data?.media as unknown as File); + + await handleMint( + reference.id, + file, + activeAccountId as string, + wallet, + reference.media_url as string, + data.title + ); }; const form = useForm>({ - resolver: zodResolver(formSchema) + resolver: zodResolver(formSchema), }); async function handleMint( reference: string, media: Promise, activeAccountId: string, - wallet: Wallet + wallet: Wallet, + mediaUrl: string, + nftTitle: string ) { + const callbackArgs = { + contractAddress: MintbaseWalletSetup.contractAddress.toString(), + amount: 1, + ref: `${reference}`, + mediaUrl: mediaUrl, + title: nftTitle, + }; + if (reference) { await wallet.signAndSendTransaction({ signerId: activeAccountId, receiverId: proxyAddress, + callbackUrl: cbUrl(reference, callbackArgs), actions: [ { type: "FunctionCall", params: { methodName: "mint", args: { - metadata: JSON.stringify({ reference, media: (await media).id }), - nft_contract_id: MintbaseWalletSetup.contractAddress, + metadata: JSON.stringify({ + reference, + media: (await media).id, + }), + nft_contract_id: MintbaseWalletSetup.contractAddress, }, gas: "200000000000000", deposit: "10000000000000000000000", @@ -91,4 +116,4 @@ const useMintImage = () => { return { form, onSubmit, preview, setPreview }; }; -export default useMintImage; \ No newline at end of file +export default useMintImage; diff --git a/minter/src/hooks/utils.ts b/minter/src/hooks/utils.ts index 9414950b..b9565f94 100644 --- a/minter/src/hooks/utils.ts +++ b/minter/src/hooks/utils.ts @@ -2,6 +2,18 @@ import { ChangeEvent } from "react"; + + +export enum TransactionSuccessEnum { + MINT = 'mint', +} + +interface CallbackArgs { + contractAddress: string; + amount: number; + ref: string; +} + export function getImageData(event: ChangeEvent) { // FileList is immutable, so we need to create a new one const dataTransfer = new DataTransfer(); @@ -15,4 +27,25 @@ export function getImageData(event: ChangeEvent) { const displayUrl = URL.createObjectURL(event.target.files![0]); return { files, displayUrl }; -} \ No newline at end of file +} + + +export const callbackUrl = ( + hash: string, + transactionType: TransactionSuccessEnum, + args: CallbackArgs +) => + `${ + window.location.origin + }/?signMeta=${encodeURIComponent( + JSON.stringify({ + type: transactionType, + args: args, + }) + )}` + + + +export const cbUrl = (hash: string, callbackArgs: CallbackArgs) => + callbackUrl(hash, TransactionSuccessEnum.MINT, callbackArgs) +