diff --git a/src/assets/coin-svgrepo-com.svg b/src/assets/coin-svgrepo-com.svg new file mode 100644 index 0000000..0e45b86 --- /dev/null +++ b/src/assets/coin-svgrepo-com.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/constants/chain.ts b/src/constants/chain.ts index 02126ea..17d3e90 100644 --- a/src/constants/chain.ts +++ b/src/constants/chain.ts @@ -46,6 +46,27 @@ export const ContractAddressMap: Record = { 84532: "0x0483e88cd38cccb71c9c1020faf830d905542d09", //baseSepolia }; +export const SBTAddressMap: Record = { + 421614: "0x6889695c0A6272410Af24766bbf3980F2466a1dc", //arbitrumSepolia + 11155420: "0x2e842f6e3fbf093981938f4e24892185bbdad727", //optimismSepolia + 84532: "0x078f00401536a55E973d4a2Cf26a9B1f98544d52", //baseSepolia +}; + +export const SBTBrowserMap: Record< + number, + (id?: number, hash?: string) => string +> = { + 421614: function (id?: number) { + return `https://testnets.opensea.io/zh-CN/assets/arbitrum-sepolia/0x6889695c0a6272410af24766bbf3980f2466a1dc/${id}`; + }, + 11155420: function (_id?: number, hash?: string) { + return `https://optimism-sepolia.blockscout.com/token/${hash}`; + }, + 84532: function (id?: number) { + return `https://testnets.opensea.io/zh-CN/assets/base-sepolia/0x078f00401536a55e973d4a2cf26a9b1f98544d52/${id}`; + }, +}; + export const FaucetMap: Record[]> = { 421614: [ { "Faucet by Alchemy": "https://www.alchemy.com/faucets/arbitrum-sepolia" }, diff --git a/src/constants/contract.ts b/src/constants/contract.ts index 8372dc4..057fdc1 100644 --- a/src/constants/contract.ts +++ b/src/constants/contract.ts @@ -199,3 +199,311 @@ export const ABI = [ type: "function", }, ]; + +export const SBTABI = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { inputs: [], name: "AlreadyMint", type: "error" }, + { inputs: [], name: "NotShortGenius", type: "error" }, + { inputs: [], name: "Soulbound", type: "error" }, + { inputs: [], name: "TokenNotExist", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "approved", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { indexed: false, internalType: "bool", name: "approved", type: "bool" }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "recipent", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenID", + type: "uint256", + }, + ], + name: "MintSuccess", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "from", type: "address" }, + { indexed: true, internalType: "address", name: "to", type: "address" }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "userAddr", type: "address" }], + name: "checkTokenID", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "checkTokenValid", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "contractURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "operator", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "userAddr", type: "address" }], + name: "mint", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "_approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "index", type: "uint256" }], + name: "tokenByIndex", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "index", type: "uint256" }, + ], + name: "tokenOfOwnerByIndex", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "tokenURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "sbtLink", type: "string" }], + name: "updateSBTFigure", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "zkMazeContract", type: "address" }, + ], + name: "updatezkMazeContract", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +]; diff --git a/src/pages/game/_components/GameOver.tsx b/src/pages/game/_components/GameOver.tsx index 4fc9665..25ecc56 100644 --- a/src/pages/game/_components/GameOver.tsx +++ b/src/pages/game/_components/GameOver.tsx @@ -9,7 +9,7 @@ import { } from "wagmi"; import { useState, useEffect, useRef } from "react"; import FileSaver from "file-saver"; -import { generatePublicInput, gameState } from "../_utils"; +import { generatePublicInput, gameState, useEvmSbt } from "../_utils"; import { PROGRAM_STRING, ABI, @@ -18,7 +18,11 @@ import { idlFactory, FaucetMap, } from "@/constants"; -import { useCurrentChain, useEVMContractAddress } from "../_utils"; +import { + useCurrentChain, + useEVMContractAddress, + useEVMSBTBrowser, +} from "../_utils"; import * as myWorker from "../_utils/zkpWorker.ts"; import { useWriteContract } from "wagmi"; import { useStateStore } from "@/store"; @@ -34,6 +38,7 @@ import { Transaction, } from "@solana/web3.js"; import useSolana from "../_utils/useSolana.ts"; +import SBTIcon from "@/assets/coin-svgrepo-com.svg?react"; const VerifyPayloadSchema: borsh.Schema = { struct: { @@ -105,6 +110,22 @@ export const GameOver = ({ useSolana(); const [solanaContractSuccess, setSolanaContractSuccess] = useState(false); + // SBT + const [mintHash, setMintHash] = useState(); + const { hasSBT, mint, mintLoading, refetchSBT } = useEvmSbt(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + const SBTBrowser = useEVMSBTBrowser(Number(hasSBT), mintHash); + const mintSBTResult = useWaitForTransactionReceipt({ + hash: mintHash as `0x${string}`, + }); + + useEffect(() => { + if (network !== "solana") { + console.log("refetchSBT", hasSBT); + void refetchSBT(); + } + }, [mintSBTResult, mintHash, refetchSBT, hasSBT, network]); + useEffect(() => { if (network !== "solana" && contractResult?.status === "success") { console.log("onRefresh contractResult", contractResult); @@ -515,6 +536,10 @@ export const GameOver = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [step]); + useEffect(() => { + console.log("hasSBT=", hasSBT); + }, [hasSBT]); + return (
@@ -584,6 +609,30 @@ export const GameOver = ({ [Browse Transaction] )} + {SettlementOver && + gameResult === 1 && + (hasSBT ? ( + + ) : ( + + ))} {(SettlementOver || errorMsg) && (