diff --git a/src/constants/badge.ts b/src/constants/badge.ts index fcdc3932..c3d591f8 100644 --- a/src/constants/badge.ts +++ b/src/constants/badge.ts @@ -1,17 +1,18 @@ import { ethers } from "ethers" import ScrollOriginsNFTABI from "@/assets/abis/ScrollOriginsNFT.json" -import { isMainnet, requireEnv } from "@/utils" - -import { ANNOUNCING_SCROLL_ORIGINS_NFT, DESIGNING_SCROLL_ORIGINS } from "./nft" +import { requireEnv } from "@/utils" const SCROLL_ORIGINS_NFT = requireEnv("REACT_APP_SCROLL_ORIGINS_NFT") const SCROLL_ORIGINS_NFT_V2 = requireEnv("REACT_APP_SCROLL_ORIGINS_NFT_V2") +const ETHEREUM_YEAR_BADGE_ADDRESS = "0x3dacAd961e5e2de850F5E027c70b56b5Afa5DfeD" +const ETHEREUM_YEAR_ATTESTER_PROXY_ADDRESS = "0x39fb5E85C7713657c2D9E869E974FF1e0B06F20C" +const ETHEREUM_YEAR_BASE_URL = `${requireEnv("REACT_APP_CANVAS_BACKEND_URI")}/badge` + export interface Badge { name: string - description: any - metaDescription?: string + description: string image: string issuer: { origin: string @@ -35,42 +36,6 @@ export interface Badge { // issued by Scroll native?: boolean } - -// TODO: only keep OriginsNFTBadge and EthereumYearBadge -export const BADGES_ADDRESS = { - sepolia: { - SCROLL_SIMPLE_BADGE_A_ADDRESS: "0x30C98067517f8ee38e748A3aF63429974103Ea6B", - SCROLL_SIMPLE_BADGE_B_ADDRESS: "0xeBFc9B95328B2Cdb3c4CA8913e329c101d2Abbc2", - SCROLL_SIMPLE_BADGE_C_ADDRESS: "0x64492EF5a60245fbaF65F69782FCf158F3a8e3Aa", - - SCROLL_ORIGINS_BADGE_ADDRESS: "0x2A3aC1337845f8C02d2dD7f80Dada22f01b569f9", - - ETHEREUM_YEAR_BADGE_ADDRESS: "0xB59B6466B21a089c93B14030AF88b164905a58fd", - ETHEREUM_YEAR_ATTESTER_PROXY_ADDRESS: "0xdAe8D9a30681899C305534849e138579aF0BF88e", - }, - mainnet: { - SCROLL_SIMPLE_BADGE_A_ADDRESS: "0xB1Dbd079c62d181926E5A54932Bb1b15F760e8A0", - SCROLL_SIMPLE_BADGE_B_ADDRESS: "0xe626E631BdDcd985D02D2eEe4fbdF901b52AE33C", - SCROLL_SIMPLE_BADGE_C_ADDRESS: "0xe485f8fcBf3b678e83d208fa3f1933a315d58356", - - SCROLL_ORIGINS_BADGE_ADDRESS: "0x2dBce60ebeAafb77e5472308f432F78aC3AE07d9", - - ETHEREUM_YEAR_BADGE_ADDRESS: "0x3dacAd961e5e2de850F5E027c70b56b5Afa5DfeD", - ETHEREUM_YEAR_ATTESTER_PROXY_ADDRESS: "0x39fb5E85C7713657c2D9E869E974FF1e0B06F20C", - }, -} - -const { - SCROLL_SIMPLE_BADGE_A_ADDRESS, - SCROLL_SIMPLE_BADGE_B_ADDRESS, - SCROLL_SIMPLE_BADGE_C_ADDRESS, - SCROLL_ORIGINS_BADGE_ADDRESS, - ETHEREUM_YEAR_BADGE_ADDRESS, - ETHEREUM_YEAR_ATTESTER_PROXY_ADDRESS, -} = BADGES_ADDRESS[isMainnet ? "mainnet" : "sepolia"] - -const ETHEREUM_YEAR_BASE_URL = `${requireEnv("REACT_APP_CANVAS_BACKEND_URI")}/badge` - export const ETHEREUM_YEAR_BADGE = { name: "Ethereum Year Badge", badgeContract: ETHEREUM_YEAR_BADGE_ADDRESS, @@ -86,79 +51,18 @@ export const ETHEREUM_YEAR_BADGE = { }, baseURL: ETHEREUM_YEAR_BASE_URL, } - -// TODO: delete -export const EAMPLE_BADGES = [ - { - name: "Pudgy Penguin #1", - badgeContract: SCROLL_SIMPLE_BADGE_A_ADDRESS, - description: "A collection 8888 Cute Chubby Pudgy Penquins sliding around on the freezing ETH blockchain.", - image: "/imgs/canvas/Penguin1.webp", - native: true, - eligibilityCheck: true, - issuer: { - origin: "https://scroll.io", - name: "Scroll", - logo: "https://scroll.io/static/media/Scroll_Logomark.673577c8260b63ae56867bc9af6af514.svg", - }, - }, - { - name: "Pudgy Penguin #2", - badgeContract: SCROLL_SIMPLE_BADGE_B_ADDRESS, - description: "A collection 8888 Cute Chubby Pudgy Penquins sliding around on the freezing ETH blockchain.", - image: "https://cloudflare-ipfs.com/ipfs/QmNf1UsmdGaMbpatQ6toXSkzDpizaGmC9zfunCyoz1enD5/penguin/2.png", - native: true, - issuer: { - origin: "https://scroll.io", - name: "Scroll", - logo: "https://scroll.io/static/media/Scroll_Logomark.673577c8260b63ae56867bc9af6af514.svg", - }, +// only keep OriginsNFTBadge +export const ORIGINS_NFT_BADGE = { + nftAddress: [SCROLL_ORIGINS_NFT, SCROLL_ORIGINS_NFT_V2], + nftAbi: ScrollOriginsNFTABI, + validator: async (provider, address) => { + const nftContract = new ethers.Contract(SCROLL_ORIGINS_NFT, ScrollOriginsNFTABI, provider) + const nftV2Contract = new ethers.Contract(SCROLL_ORIGINS_NFT_V2, ScrollOriginsNFTABI, provider) + + let balance = await nftContract.balanceOf(address) + if (!balance) { + balance = await nftV2Contract.balanceOf(address) + } + return !!balance }, - { - name: "Pudgy Penguin #3", - badgeContract: SCROLL_SIMPLE_BADGE_C_ADDRESS, - description: - "AlienSwap is a multi-chain NFT marketplace and aggregator aimed to build the leading trading layer for the community, now we have integrated Scroll not only with the marketplace, but also our CreateX NFT creation platform, so that any one can create, list or trade NFT on Scroll.", - image: "https://cloudflare-ipfs.com/ipfs/QmNf1UsmdGaMbpatQ6toXSkzDpizaGmC9zfunCyoz1enD5/penguin/3.png", - native: true, - issuer: { - origin: "https://scroll.io", - name: "Scroll", - logo: "https://scroll.io/static/media/Scroll_Logomark.673577c8260b63ae56867bc9af6af514.svg", - }, - }, -] - -export const SCROLL_BADGES = [ - ETHEREUM_YEAR_BADGE, - { - name: "Scroll Origins NFT", - nftAddress: [SCROLL_ORIGINS_NFT, SCROLL_ORIGINS_NFT_V2], - nftAbi: ScrollOriginsNFTABI, - badgeContract: SCROLL_ORIGINS_BADGE_ADDRESS, - metaDescription: - "The Scroll Origins NFT is a soulbound NFT that serves to recognize the early builders of Scroll. Eligible minters have deployed a smart contract to Scroll Mainnetwithin 60 days of the Genesis Block. Higher levels of rarity are are rewarded to smart contracts that have contributed significant levels of interaction or value to the network.", - description: ` - [Scroll Origins](${ANNOUNCING_SCROLL_ORIGINS_NFT}) is a [specially designed NFT](${DESIGNING_SCROLL_ORIGINS}) - program to celebrate alongside early developers building on Scroll within 60 days of Genesis Block (Before December 9, 2023 10:59PM GMT). - `, - image: "/imgs/canvas/OriginsNFT.svg", - native: true, - originsNFT: true, - issuer: { - origin: "https://scroll.io", - name: "Scroll", - logo: "https://scroll.io/static/media/Scroll_Logomark.673577c8260b63ae56867bc9af6af514.svg", - }, - validator: async (provider, address) => { - const nftContract = new ethers.Contract(SCROLL_ORIGINS_NFT, ScrollOriginsNFTABI, provider) - const nftV2Contract = new ethers.Contract(SCROLL_ORIGINS_NFT_V2, ScrollOriginsNFTABI, provider) - - let balance = await nftContract.balanceOf(address) - if (!balance) { - balance = await nftV2Contract.balanceOf(address) - } - return !!balance - }, - }, -] +} diff --git a/src/pages/bridge/components/MintBadge/index.tsx b/src/pages/bridge/components/MintBadge/index.tsx index 475cc3d2..cac54c87 100644 --- a/src/pages/bridge/components/MintBadge/index.tsx +++ b/src/pages/bridge/components/MintBadge/index.tsx @@ -8,7 +8,7 @@ import BadgeDetailDialog from "@/pages/canvas/Dashboard/BadgeDetailDialog" import { checkIfHasBadgeByAddress } from "@/services/canvasService" import useCanvasStore, { BadgeDetailDialogType } from "@/stores/canvasStore" -//TODO: Scroll hasn’t issued a badge according to the bridge yet, so there’s no need to display Scrolly +//TODO: Scroll hasn’t issued a badge according to the badge yet, so there’s no need to display Scrolly const MintBadge = () => { const { changeBadgeDetailDialog, changeSelectedBadge, profileMinted } = useCanvasStore() const badge: Badge = ETHEREUM_YEAR_BADGE diff --git a/src/pages/canvas-badge/Badges/BadgeList/index.tsx b/src/pages/canvas-badge/Badges/BadgeList/index.tsx index a8467fea..7fe274c9 100644 --- a/src/pages/canvas-badge/Badges/BadgeList/index.tsx +++ b/src/pages/canvas-badge/Badges/BadgeList/index.tsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom" import { usePrevious } from "react-use" import "react-virtualized/styles.css" -import { Box, Button, SvgIcon } from "@mui/material" +import { Box, Button, CircularProgress, SvgIcon } from "@mui/material" import { fetchBadgesURL } from "@/apis/canvas-badge" import { ReactComponent as ArrowDownSvg } from "@/assets/svgs/canvas-badge/arrow-down.svg" @@ -133,7 +133,13 @@ const BadgeList = props => { }, }} onClick={onAddPage} - endIcon={} + endIcon={ + loading ? ( + + ) : ( + + ) + } > Load more diff --git a/src/pages/canvas/badge/BadgeDetail.tsx b/src/pages/canvas/badge/BadgeDetail.tsx index 1db020d2..9a1a7d52 100644 --- a/src/pages/canvas/badge/BadgeDetail.tsx +++ b/src/pages/canvas/badge/BadgeDetail.tsx @@ -222,7 +222,11 @@ const BadgeDetail = props => { gap: "0.8rem", }} > - + {detail.issuer?.name || "Unknown"} @@ -262,7 +266,7 @@ const BadgeDetail = props => { )} - {!property.includes("owner") && !detail.native && ( + {!property.includes("owner") && detail.thirdParty && ( warning image Issuing badge is permissionless - perform due diligence and interact with dApps at your own risk. diff --git a/src/pages/canvas/badge/index.tsx b/src/pages/canvas/badge/index.tsx index 8cd150e2..c05220c0 100644 --- a/src/pages/canvas/badge/index.tsx +++ b/src/pages/canvas/badge/index.tsx @@ -20,7 +20,7 @@ import { upgradeBadge, } from "@/services/canvasService" import useCanvasStore from "@/stores/canvasStore" -import { formatDate, generateShareTwitterURL, requireEnv } from "@/utils" +import { formatDate, generateShareTwitterURL, isOriginsNFTBadge, requireEnv } from "@/utils" import BackToCanvas from "./BackToCanvas" import BadgeDetail from "./BadgeDetail" @@ -86,12 +86,13 @@ const BadgeDetailPage = () => { const { badgeContract, description, ...badgeMetadata } = await fillBadgeDetailWithPayload(publicProvider, { id, data }) const notionBadge = await fetchNotionBadgeByAddr(badgeContract) + console.log(notionBadge, "notionBadge") + const name = await fetchProfileUsername(publicProvider, recipient) let upgradable = false if (walletCurrentAddress === recipient) { const checkedBadge = await checkBadgeUpgradable(publicProvider, { id, badgeContract }) upgradable = checkedBadge.upgradable - // upgradable = true } const badgeDetail = { ...badgeMetadata, @@ -101,12 +102,11 @@ const BadgeDetailPage = () => { mintedOn: formatDate(time * 1000), badgeContract, issuer: notionBadge.issuer, - description: notionBadge.originsNFT ? notionBadge.description : description, + description: isOriginsNFTBadge(notionBadge.badgeContract) ? notionBadge.description : description, upgradable, - originsNFT: notionBadge.originsNFT, - native: notionBadge.native, + thirdParty: notionBadge.thirdParty, } - if (notionBadge.originsNFT) { + if (isOriginsNFTBadge(notionBadge.badgeContract)) { const rarityNum = badgeMetadata.attributes.find(item => item.trait_type === "Rarity").value badgeDetail.rarity = NFT_RARITY_MAP[rarityNum] } @@ -149,7 +149,7 @@ const BadgeDetailPage = () => { detail={detail} metadata={metadata} loading={loading} - property={["owner", "issuer", "mintedOn", detail.originsNFT ? "rarity" : undefined]} + property={["owner", "issuer", "mintedOn", isOriginsNFTBadge(detail.badgeContract) ? "rarity" : undefined]} breadcrumb={} onUpgrade={handleUpgradeBadge} > @@ -157,14 +157,14 @@ const BadgeDetailPage = () => { View on EAS - {detail.native ? ( - - Visit my canvas - - ) : ( + {detail.thirdParty && detail.issuer?.name ? ( Visit {detail.issuer?.name} + ) : ( + + Visit my canvas + )} diff --git a/src/pages/canvas/badgeContract/index.tsx b/src/pages/canvas/badgeContract/index.tsx index 07375e4d..399b033f 100644 --- a/src/pages/canvas/badgeContract/index.tsx +++ b/src/pages/canvas/badgeContract/index.tsx @@ -269,7 +269,7 @@ const BadgeContractDetail = props => { }> {renderAction()} - {badgeForMint.native ? ( + {badgeForMint.thirdParty ? ( ({ @@ -277,9 +277,10 @@ const BadgeContractDetail = props => { gridColumn: "span 2", }, })} - href="/canvas" + href={badgeForMint.issuer?.origin} + target="_blank" > - Visit my canvas + Visit {badgeForMint.issuer?.name} ) : ( { gridColumn: "span 2", }, })} - href={badgeForMint.issuer?.origin} - target="_blank" + href="/canvas" > - Visit {badgeForMint.issuer?.name} + Visit my canvas )} diff --git a/src/services/canvasService.ts b/src/services/canvasService.ts index cdaf8f6c..d4741904 100644 --- a/src/services/canvasService.ts +++ b/src/services/canvasService.ts @@ -7,14 +7,22 @@ import AttestProxyABI from "@/assets/abis/CanvasAttestProxy.json" import BadgeABI from "@/assets/abis/CanvasBadge.json" import ProfileABI from "@/assets/abis/CanvasProfile.json" import ProfileRegistryABI from "@/assets/abis/CanvasProfileRegistry.json" -import { SCROLL_BADGES } from "@/constants" -import { checkDelegatedAttestation, decodeBadgePayload, isUserRejected, recognizeError, requireEnv, sentryDebug, trimErrorMessage } from "@/utils" +import { ORIGINS_NFT_BADGE } from "@/constants" +import { + checkDelegatedAttestation, + decodeBadgePayload, + isOriginsNFTBadge, + isUserRejected, + recognizeError, + requireEnv, + sentryDebug, + trimErrorMessage, +} from "@/utils" const EAS_GRAPHQL_URL = requireEnv("REACT_APP_EAS_GRAPHQL_URL") -const BADGE_SCHEMA = requireEnv("REACT_APP_BADGE_SCHEMA") -const SCROLL_SEPOLIA_EAS_ADDRESS = requireEnv("REACT_APP_EAS_ADDRESS") -const SCROLL_SEPOLIA_BADGE_SCHEMA = requireEnv("REACT_APP_BADGE_SCHEMA") +const SCROLL_EAS_ADDRESS = requireEnv("REACT_APP_EAS_ADDRESS") +const SCROLL_BADGE_SCHEMA = requireEnv("REACT_APP_BADGE_SCHEMA") const PROFILE_REGISTRY_ADDRESS = requireEnv("REACT_APP_PROFILE_REGISTRY_ADDRESS") @@ -35,7 +43,7 @@ const queryUserBadges = async userAddress => { query Attestation { attestations( where: { - schemaId: { equals: "${BADGE_SCHEMA}" }, + schemaId: { equals: "${SCROLL_BADGE_SCHEMA}" }, recipient: { equals: "${userAddress}" }, revoked: { equals: false }, } @@ -112,7 +120,7 @@ const getBadgeMetadata = async (provider, badgeContractAddress, badgeUID = ether const contract = new ethers.Contract(badgeContractAddress, BadgeABI, provider) const badgeMetadataURI = await contract.badgeTokenURI(badgeUID) let badgeImageURI = badgeMetadataURI.replace(/^ipfs:\/\/(.*)/, "https://ipfs.io/ipfs/$1") - const metadata = await scrollRequest(badgeImageURI) + const metadata = await scrollRequest(badgeImageURI, { timeout: 5e3 }) return metadata } catch (error) { // console.log("Failed to get badge image URI:", error) @@ -253,10 +261,12 @@ const fetchCanvasDetail = async (privider, othersAddress, profileAddress) => { const checkBadgeEligibility = async (provider, walletAddress, badge: any) => { try { // originsNFT - if (badge.validator) { - const eligibility = await badge.validator(provider, walletAddress) + if (isOriginsNFTBadge(badge.badgeContract)) { + const { validator } = ORIGINS_NFT_BADGE + const eligibility = await validator(provider, walletAddress) return eligibility } + if (!badge.baseURL && !badge.eligibilityCheck) { return true } @@ -310,9 +320,9 @@ const mintOriginNFTBadge = async (signer, walletCurrentAddress, badgeAddress, nf const abiCoder = new AbiCoder() const originsBadgePayload = abiCoder.encode(["address", "uint256"], [nftAddress[nftVersion], tokenId]) const badgePayload = abiCoder.encode(["address", "bytes"], [badgeAddress, originsBadgePayload]) - const easContract = new ethers.Contract(SCROLL_SEPOLIA_EAS_ADDRESS, AttestProxyABI, signer) + const easContract = new ethers.Contract(SCROLL_EAS_ADDRESS, AttestProxyABI, signer) const attestParams = { - schema: SCROLL_SEPOLIA_BADGE_SCHEMA, + schema: SCROLL_BADGE_SCHEMA, data: { recipient: walletCurrentAddress, expirationTime: 0, @@ -334,9 +344,9 @@ const mintOriginNFTBadge = async (signer, walletCurrentAddress, badgeAddress, nf const mintPermissionlessBadge = async (signer, walletCurrentAddress, badgeAddress) => { const abiCoder = new AbiCoder() const badgePayload = abiCoder.encode(["address", "bytes"], [badgeAddress, "0x"]) - const easContract = new ethers.Contract(SCROLL_SEPOLIA_EAS_ADDRESS, AttestProxyABI, signer) + const easContract = new ethers.Contract(SCROLL_EAS_ADDRESS, AttestProxyABI, signer) const attestParams = { - schema: SCROLL_SEPOLIA_BADGE_SCHEMA, + schema: SCROLL_BADGE_SCHEMA, data: { recipient: walletCurrentAddress, expirationTime: 0, @@ -358,14 +368,15 @@ const mintPermissionlessBadge = async (signer, walletCurrentAddress, badgeAddres const mintBadge = async (provider, walletCurrentAddress, badge) => { try { - const { badgeContract, nftAddress, nftAbi, attesterProxy, baseURL } = badge + const { badgeContract, attesterProxy, baseURL } = badge const signer = await provider!.getSigner(0) // Origins NFT Badge - if (nftAddress) { + if (isOriginsNFTBadge(badgeContract)) { + const { nftAddress, nftAbi } = ORIGINS_NFT_BADGE return await mintOriginNFTBadge(signer, walletCurrentAddress, badgeContract, nftAddress, nftAbi) } - // Third Party Badge + // Backend-authorized Badges if (attesterProxy) { return await mintBackendAuthorizedBadge(signer, walletCurrentAddress, badgeContract, attesterProxy, baseURL) } @@ -484,11 +495,6 @@ const fetchNotionBadgeByAddr = async addr => { if (!addr) { return {} } - // TODO: store scroll badges in notion db - const scrollBadge = SCROLL_BADGES.find(item => item.badgeContract === addr) - if (scrollBadge) { - return scrollBadge - } const data = await scrollRequest(fetchBadgeByAddrURL(addr)) return data } catch (e) { diff --git a/src/stores/canvasStore.ts b/src/stores/canvasStore.ts index b8ca1895..a59da6be 100644 --- a/src/stores/canvasStore.ts +++ b/src/stores/canvasStore.ts @@ -2,7 +2,7 @@ import { Contract } from "ethers" import produce from "immer" import { create } from "zustand" -import { Badge, ETHEREUM_YEAR_BADGE, SCROLL_BADGES } from "@/constants" +import { Badge, ETHEREUM_YEAR_BADGE } from "@/constants" import { checkBadgeEligibility, checkBadgeUpgradable, @@ -163,7 +163,7 @@ const useCanvasStore = create()((set, get) => ({ inputReferralCode: ["", "", "", "", ""], - badgeList: SCROLL_BADGES, + badgeList: [], checkIfProfileMinted: async (registryInstance, userAddress, test) => { try { @@ -478,21 +478,12 @@ const useCanvasStore = create()((set, get) => ({ set({ pickUpgradableBadgesLoading: false, - // upgradableBadges: userBadges.slice(0, 2).map(item => ({ ...item, upgradable: true })), upgradableBadges: finalUpgradableBadges, }) }, - // jointBadgeList: async () => { - // const { badges } = await scrollRequest(BADGE_LIST_URL) - // set({ - // badgeList: [...SCROLL_BADGES.slice(0, SCROLL_BADGES.length - 1), ...badges, SCROLL_BADGES[SCROLL_BADGES.length - 1]], - // }) - // }, - addFirstBadge: async (providerOrSigner, badgeId, badgeImage, badgeContract) => { set({ - // queryUsernameLoading: true, userBadges: [{ id: badgeId, name: ETHEREUM_YEAR_BADGE.name, description: ETHEREUM_YEAR_BADGE.description, image: badgeImage, badgeContract }], attachedBadges: [badgeId], orderedAttachedBadges: [badgeId], diff --git a/src/utils/canvas.ts b/src/utils/canvas.ts index 9955bd5f..455d1fb1 100644 --- a/src/utils/canvas.ts +++ b/src/utils/canvas.ts @@ -7,6 +7,8 @@ import { requireEnv } from "@/utils" const SCROLL_SEPOLIA_BADGE_SCHEMA = requireEnv("REACT_APP_BADGE_SCHEMA") +const SCROLL_ORIGINS_BADGE_ADDRESS = "0x2dBce60ebeAafb77e5472308f432F78aC3AE07d9" + export const getBadgeImgURL = image => { if (!image) return "" return image.replace(/^ipfs:\/\/(.*)/, "https://cloudflare-ipfs.com/ipfs/$1") @@ -93,3 +95,7 @@ export const checkDelegatedAttestation = (tx, proxyAddress) => { } return } + +export const isOriginsNFTBadge = badgeContract => { + return badgeContract === SCROLL_ORIGINS_BADGE_ADDRESS +}