diff --git a/ai-minter/package.json b/ai-minter/package.json index f5884e1c..919f0a9a 100644 --- a/ai-minter/package.json +++ b/ai-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-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", diff --git a/ai-minter/pnpm-lock.yaml b/ai-minter/pnpm-lock.yaml index 8192157e..2460ee64 100644 --- a/ai-minter/pnpm-lock.yaml +++ b/ai-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) @@ -255,10 +255,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) @@ -281,8 +281,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 @@ -290,8 +290,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 @@ -299,8 +299,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/ai-minter/public/thumbnail.png b/ai-minter/public/thumbnail.png new file mode 100644 index 00000000..5975450d Binary files /dev/null and b/ai-minter/public/thumbnail.png differ diff --git a/ai-minter/src/app/favicon.ico b/ai-minter/src/app/favicon.ico index 718d6fea..fc3cf89a 100644 Binary files a/ai-minter/src/app/favicon.ico and b/ai-minter/src/app/favicon.ico differ diff --git a/ai-minter/src/app/layout.tsx b/ai-minter/src/app/layout.tsx index a27ea695..6bfda0f9 100644 --- a/ai-minter/src/app/layout.tsx +++ b/ai-minter/src/app/layout.tsx @@ -1,30 +1,64 @@ -"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 { MintbaseWalletSetup } from "@/config/setup"; -import "@near-wallet-selector/modal-ui/styles.css"; -import { ReplicateProvider } from "@/providers/replicate"; -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://ai-minter.mintbase.xyz"), + title: pageTitle, + openGraph: { + title: pageTitle, + description: pageDescription, + images: ["./thumbnail.png"], + }, + twitter: { + title: pageTitle, + description: pageDescription, + siteId: "1467726470533754880", + creator: "Mintbase", + card: "summary_large_image", + images: "./thumbnail.png", + }, + }; +} export default function RootLayout({ children, }: { children: React.ReactNode; }) { - return ( - - - - -
- {children} -
- - -
-
- ); + return {children} ; } diff --git a/ai-minter/src/app/page.tsx b/ai-minter/src/app/page.tsx index a6894ca6..1c9108a7 100644 --- a/ai-minter/src/app/page.tsx +++ b/ai-minter/src/app/page.tsx @@ -5,9 +5,50 @@ 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"; +import { getTxnHash } from "@/hooks/utils"; +import { useEffect, useState } from "react"; + export default function Home() { const { isConnected } = useMbWallet(); + const [txnUrl, setTxnUrl] = useState(""); + + + const params = useSearchParams(); + + const mintedParams = params.get("signMeta") + ? JSON.parse(params.get("signMeta") as string) + : ""; +const txnHashes = params.get("transactionHashes") + ? params.get("transactionHashes") + : ""; + + useEffect(() => { + const fetchTxnHash = async () => { + const txn = await getTxnHash(txnHashes as string); + setTxnUrl(txn); + }; + + fetchTxnHash(); + }, [txnHashes]); + + if (mintedParams) { + const metaPage = `${mbUrl}/ref/${mintedParams.args.ref}?type=meta`; + const txnHashUrl = `${nearblocksUrl}/txns/${txnUrl}`; + + + const successPageData = { + nftTitle: mintedParams.args.title as string, + mediaUrl: mintedParams.args.mediaUrl as string, + metaPage, + txnHashUrl, + }; + + return ; + } if (isConnected) return ( @@ -22,15 +63,15 @@ export default function Home() {
- Mintbase - Simple Minter Example + Mintbase - Simple AI Minter Example
-

Mintbase Minter

+

Mintbase AI Minter

- A simple NFT Minter on Mintbase + A simple NFT AI Minter on Mintbase

diff --git a/ai-minter/src/components/Provider.tsx b/ai-minter/src/components/Provider.tsx new file mode 100644 index 00000000..8804e1d8 --- /dev/null +++ b/ai-minter/src/components/Provider.tsx @@ -0,0 +1,30 @@ +"use client" + +import { MintbaseWalletContextProvider } from "@mintbase-js/react"; +import { MintbaseWalletSetup } from "@/config/setup"; +import "@near-wallet-selector/modal-ui/styles.css"; +import { ReplicateProvider } from "@/providers/replicate"; +import { Inter } from "next/font/google"; + +const inter = Inter({ subsets: ["latin"] }); + +export const AppProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + + return ( + + + + +
+ {children} +
+ + +
+
+ ) +} \ No newline at end of file diff --git a/ai-minter/src/components/Success.tsx b/ai-minter/src/components/Success.tsx new file mode 100644 index 00000000..b1cd633b --- /dev/null +++ b/ai-minter/src/components/Success.tsx @@ -0,0 +1,47 @@ +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/ai-minter/src/config/setup.ts b/ai-minter/src/config/setup.ts index 063f01ef..e6d94802 100644 --- a/ai-minter/src/config/setup.ts +++ b/ai-minter/src/config/setup.ts @@ -1,7 +1,22 @@ 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 nearblocksApi = !isTestnet + ? "https://api.nearblocks.io" + : "https://api-testnet.nearblocks.io"; export const MintbaseWalletSetup = { contractAddress, diff --git a/ai-minter/src/hooks/useMint.ts b/ai-minter/src/hooks/useMint.ts index 143825cd..bc9d7c48 100644 --- a/ai-minter/src/hooks/useMint.ts +++ b/ai-minter/src/hooks/useMint.ts @@ -11,6 +11,7 @@ import { ArweaveResponse, uploadFile, uploadReference } from "@mintbase-js/stora import { formSchema } from "./formSchema"; import { MintbaseWalletSetup, proxyAddress } from "@/config/setup"; import { Wallet } from "@near-wallet-selector/core"; +import { cbUrl } from "./utils"; const useMintImage = () => { const { selector, activeAccountId } = useMbWallet(); @@ -56,7 +57,14 @@ const useMintImage = () => { }); const file = uploadFile(media); - await handleMint(reference.id, file, activeAccountId as string, wallet); + await handleMint( + reference.id, + file, + activeAccountId as string, + wallet, + reference.media_url as string, + data.title + ); }; const form = useForm>({ @@ -67,12 +75,26 @@ const useMintImage = () => { reference: string, media: Promise, activeAccountId: string, - wallet: Wallet + wallet: Wallet, + mediaUrl: string, + nftTitle: string ) { if (reference) { + + const finalMediaUrl = mediaUrl.replace("https://arweave.net/", ""); + + const callbackArgs = { + contractAddress: MintbaseWalletSetup.contractAddress.toString(), + amount: 1, + ref: `${reference}`, + mediaUrl: finalMediaUrl, + title: nftTitle, + }; + await wallet.signAndSendTransaction({ signerId: activeAccountId, receiverId: proxyAddress, + callbackUrl: cbUrl(reference, callbackArgs), actions: [ { type: "FunctionCall", diff --git a/ai-minter/src/hooks/utils.ts b/ai-minter/src/hooks/utils.ts index 9414950b..c60a19e2 100644 --- a/ai-minter/src/hooks/utils.ts +++ b/ai-minter/src/hooks/utils.ts @@ -1,7 +1,18 @@ "use client"; +import { nearblocksApi } from "@/config/setup"; 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 +26,27 @@ 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); + +export const getTxnHash = async (hash: string) => { + const res = await fetch(`${nearblocksApi}/v1/search/?keyword=${hash}`); + + const txn = await res.json(); + + return txn?.receipts[0].originated_from_transaction_hash; +}; diff --git a/minter/public/thumbnail.png b/minter/public/thumbnail.png new file mode 100644 index 00000000..e758c802 Binary files /dev/null and b/minter/public/thumbnail.png differ diff --git a/minter/src/app/layout.tsx b/minter/src/app/layout.tsx index 8e72800b..a8f82d99 100644 --- a/minter/src/app/layout.tsx +++ b/minter/src/app/layout.tsx @@ -43,7 +43,7 @@ export async function generateMetadata(): Promise { openGraph: { title:pageTitle, description: pageDescription, - images:['https://i.imgur.com/QDJPsAA.png'], + images:['./thumbnail.png'], }, twitter: { title: pageTitle, @@ -51,7 +51,7 @@ export async function generateMetadata(): Promise { siteId: "1467726470533754880", creator: "Mintbase", card: "summary_large_image", - images: 'https://i.imgur.com/QDJPsAA.png' + images: './thumbnail.png' }, }; } diff --git a/minter/src/app/page.tsx b/minter/src/app/page.tsx index 388cef42..79c14bfb 100644 --- a/minter/src/app/page.tsx +++ b/minter/src/app/page.tsx @@ -8,23 +8,37 @@ import Minter from "@/components/Minter"; import { useSearchParams } from "next/navigation"; import { SuccessPage } from "@/components/Success"; import { mbUrl, nearblocksUrl } from "@/config/setup"; +import { getTxnHash } from "@/hooks/utils"; +import { useEffect, useState } from "react"; + export default function Home() { const { isConnected } = useMbWallet(); + const [txnUrl, setTxnUrl] = useState(""); + const params = useSearchParams(); const mintedParams = params.get("signMeta") - ? JSON.parse(params.get("signMeta") as string) - : ""; - const txnHashes = params.get("transactionHashes") - ? params.get("transactionHashes") - : ""; + ? JSON.parse(params.get("signMeta") as string) + : ""; +const txnHashes = params.get("transactionHashes") + ? params.get("transactionHashes") + : ""; + useEffect(() => { + const fetchTxnHash = async () => { + const txn = await getTxnHash(txnHashes as string); + setTxnUrl(txn); + }; + + fetchTxnHash(); + }, [txnHashes]); if (mintedParams) { const metaPage = `${mbUrl}/ref/${mintedParams.args.ref}?type=meta`; - const txnHashUrl = `${nearblocksUrl}/txns/${txnHashes}`; + const txnHashUrl = `${nearblocksUrl}/txns/${txnUrl}`; + const successPageData = { nftTitle: mintedParams.args.title as string, diff --git a/minter/src/components/Success.tsx b/minter/src/components/Success.tsx index e5a3a3d7..b1cd633b 100644 --- a/minter/src/components/Success.tsx +++ b/minter/src/components/Success.tsx @@ -21,6 +21,8 @@ interface SuccessPageData { export function SuccessPage({ data }: { data: SuccessPageData }): JSX.Element { const { nftTitle, mediaUrl, metaPage, txnHashUrl } = data; + + return ( diff --git a/minter/src/config/setup.ts b/minter/src/config/setup.ts index f622812c..d905adef 100644 --- a/minter/src/config/setup.ts +++ b/minter/src/config/setup.ts @@ -17,6 +17,10 @@ export const nearblocksUrl = !isTestnet ? "https://nearblocks.io" : "https://testnet.nearblocks.io"; + export const nearblocksApi = !isTestnet + ? "https://api.nearblocks.io" + : "https://api-testnet.nearblocks.io"; + export const MintbaseWalletSetup = { contractAddress, network, diff --git a/minter/src/hooks/useMint.ts b/minter/src/hooks/useMint.ts index 43b0a487..97c5ca6b 100644 --- a/minter/src/hooks/useMint.ts +++ b/minter/src/hooks/useMint.ts @@ -53,8 +53,6 @@ const useMintImage = () => { media: data?.media as unknown as File, }); - console.log(reference, "reference"); - const file = uploadFile(data?.media as unknown as File); await handleMint( diff --git a/minter/src/hooks/utils.ts b/minter/src/hooks/utils.ts index b9565f94..a1fdb9aa 100644 --- a/minter/src/hooks/utils.ts +++ b/minter/src/hooks/utils.ts @@ -1,13 +1,11 @@ "use client"; +import { nearblocksApi } from "@/config/setup"; import { ChangeEvent } from "react"; - - export enum TransactionSuccessEnum { - MINT = 'mint', + MINT = "mint", } - interface CallbackArgs { contractAddress: string; amount: number; @@ -29,23 +27,25 @@ export function getImageData(event: ChangeEvent) { return { files, displayUrl }; } - export const callbackUrl = ( hash: string, transactionType: TransactionSuccessEnum, args: CallbackArgs ) => - `${ - window.location.origin - }/?signMeta=${encodeURIComponent( + `${window.location.origin}/?signMeta=${encodeURIComponent( JSON.stringify({ type: transactionType, args: args, }) - )}` + )}`; +export const cbUrl = (hash: string, callbackArgs: CallbackArgs) => + callbackUrl(hash, TransactionSuccessEnum.MINT, callbackArgs); +export const getTxnHash = async (hash: string) => { + const res = await fetch(`${nearblocksApi}/v1/search/?keyword=${hash}`); -export const cbUrl = (hash: string, callbackArgs: CallbackArgs) => - callbackUrl(hash, TransactionSuccessEnum.MINT, callbackArgs) + const txn = await res.json(); + return txn?.receipts[0].originated_from_transaction_hash; +};