Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/appGlobals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,29 @@
const scrollRequest = (url: string, options?: object) => {
const scrollRequest = (url: string, options?: RequestInit & { timeout?: number }) => {
if (options?.timeout) {
const controller = new AbortController()
const { signal } = controller
const optionsWithSignal = { ...options, signal }

const timeoutId = setTimeout(() => {
controller.abort()
}, options.timeout)

return fetch(url, optionsWithSignal)
.then(async res => {
if (res.ok) {
clearTimeout(timeoutId)
return res.json()
}
// server response but not 200
const message = await res.text()
const error = new Error(message)
error.status = res.status
clearTimeout(timeoutId)
throw error
})
.then(data => data)
}

return fetch(url, options)
.then(async res => {
if (res.ok) {
Expand Down
17 changes: 17 additions & 0 deletions src/assets/abis/CanvasBadge.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,22 @@
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"name": "recipient",
"type": "address"
}
],
"name": "isEligible",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
10 changes: 7 additions & 3 deletions src/constants/badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@ export interface Badge {
}
badgeContract: string

// third party
// Backend-authorized
attesterProxy?: string
eligibilityAPI?: string
baseUrl?: string
eligibilityCheck?: boolean

// Origin NFT
originsNFT?: boolean
validator?: (address, provider) => Promise<boolean>
nftAddress?: string[]
nftAbi?: object

// view third party badge website
// issued by Scroll
native: boolean

airdrop?: boolean
}

// TODO: only keep OriginsNFTBadge and EthereumYearBadge
Expand Down Expand Up @@ -93,6 +96,7 @@ export const EAMPLE_BADGES = [
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",
Expand Down
66 changes: 27 additions & 39 deletions src/pages/canvas/Dashboard/BadgeDetailDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const ButtonContainer = styled(forwardRef<any, any>((props, ref) => <Box {...pro
"& .mintBtn": {
gridColumn: "span 2",
},
"& .tip": {
gridColumn: "span 2",
},
},
}))

Expand Down Expand Up @@ -165,13 +168,27 @@ const BadgeDetailDialog = () => {
}
}

const renderButtonText = () => {
if (isMobile && selectedBadge.airdrop) {
return "Waiting for issuer to mint"
} else if (isBadgeMinting.get(selectedBadge.badgeContract)) {
return "Minting"
}
return "Mint badge"
const renderMintTip = () => {
return (
<>
{[BadgeDetailDialogType.NO_PROFILE].includes(badgeDetailDialogVisible) && (
<Stack className="tip" direction="row" gap="0.8rem" alignItems="center" sx={{ mb: [0, "2.4rem"], px: [0, "4rem"] }}>
<InfoOutlinedIcon sx={{ color: "#FAD880", fontSize: ["1.8rem", "2.4rem"] }} />
<Typography sx={{ color: "#FAD880", fontSize: ["1.6rem", "1.8rem"], lineHeight: ["2.4rem", "2.8rem"] }}>
You need a Scroll Canvas in order to mint your {selectedBadge.name} Badge.
</Typography>
</Stack>
)}
{[BadgeDetailDialogType.MINT, BadgeDetailDialogType.MINT_WITH_BACK].includes(badgeDetailDialogVisible) && selectedBadge.airdrop && (
<Stack className="tip" direction="row" gap="0.8rem" alignItems="center" sx={{ mb: [0, "2.4rem"], px: [0, "4rem"] }}>
<InfoOutlinedIcon sx={{ color: "#85E0D1", fontSize: ["1.8rem", "2.4rem"] }} />
<Typography sx={{ color: "#85E0D1", fontSize: ["1.6rem", "1.8rem"], lineHeight: ["2.4rem", "2.8rem"] }}>
You are eligible. Your badge will be airdroped by the issuer.
</Typography>
</Stack>
)}
</>
)
}

return (
Expand Down Expand Up @@ -248,15 +265,7 @@ const BadgeDetailDialog = () => {
</Stack>
</>
)}

{[BadgeDetailDialogType.NO_PROFILE].includes(badgeDetailDialogVisible) && (
<Stack direction="row" gap="0.8rem" alignItems="center" sx={{ mb: [0, "2.4rem"], px: [0, "4rem"] }}>
<InfoOutlinedIcon sx={{ color: "#FAD880", fontSize: ["1.8rem", "2.4rem"] }} />
<Typography sx={{ color: "#FAD880", fontSize: ["1.6rem", "1.8rem"], lineHeight: ["2.4rem", "2.8rem"] }}>
You need a Scroll Canvas in order to mint your {selectedBadge.name} Badge.
</Typography>
</Stack>
)}
{!isMobile && renderMintTip()}

<ButtonContainer ref={actionsRef}>
{[BadgeDetailDialogType.MINT, BadgeDetailDialogType.MINT_WITH_BACK].includes(badgeDetailDialogVisible) && (
Expand All @@ -267,31 +276,10 @@ const BadgeDetailDialog = () => {
color="primary"
onClick={handleMint}
>
{renderButtonText()}
{isBadgeMinting.get(selectedBadge.badgeContract) ? "Minting" : "Mint badge"}
</StyledScrollButton>
)}
{!isMobile && selectedBadge.airdrop && (
<Typography
sx={{
fontSize: "1.6rem",
position: "absolute",
color: "#85E0D1",
width: ["100%", "max-content"],
bottom: ["unser", "-1.6em"],
top: ["-2.6rem", "unset"],
px: ["2rem", 0],
textAlign: "center",
}}
>
This is an airdrop-only badge and you will receive it once the issuer mint for you.
</Typography>
)}

{/* {[BadgeDetailDialogType.UPGRADE].includes(badgeDetailDialogVisible) && (
<StyledScrollButton color="primary" onClick={handleViewEAS}>
View on EAS
</StyledScrollButton>
)} */}
{isMobile && renderMintTip()}

{[BadgeDetailDialogType.NO_PROFILE].includes(badgeDetailDialogVisible) && (
<StyledScrollButton className="viewBtn" color="primary" target="_blank" onClick={handleViewCanvas}>
Expand Down
31 changes: 21 additions & 10 deletions src/pages/canvas/badgeContract/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,17 @@ const BadgeContractDetail = props => {
</Typography>
</>
)
} else if (profileMinted && isOwned === false && isEligible && !badgeForMint.airdrop) {
} else if (profileMinted && isOwned === false && isEligible) {
if (badgeForMint.airdrop) {
return (
<>
<SvgIcon sx={{ color: "#85E0D1", fontSize: "2.4rem" }} component={ValidSvg} inheritViewBox></SvgIcon>
<Typography sx={{ color: "#85E0D1 !important", fontSize: ["1.6rem", "1.8rem"], lineHeight: ["2.4rem", "2.8rem"], fontWeight: 500 }}>
You are eligible. Your badge will be airdroped by the issuer.
</Typography>
</>
)
}
return (
<>
<SvgIcon sx={{ color: "#85E0D1", fontSize: "2.4rem" }} component={ValidSvg} inheritViewBox></SvgIcon>
Expand All @@ -133,16 +143,17 @@ const BadgeContractDetail = props => {
</Typography>
</>
)
} else if (profileMinted && isOwned === false && isEligible && badgeForMint.airdrop) {
return (
<>
<SvgIcon sx={{ color: "#85E0D1", fontSize: "2.4rem" }} component={ValidSvg} inheritViewBox></SvgIcon>
<Typography sx={{ color: "#85E0D1 !important", fontSize: ["1.6rem", "1.8rem"], lineHeight: ["2.4rem", "2.8rem"], fontWeight: 500 }}>
This is an airdrop-only badge and you will receive it once the issuer mint for you.
</Typography>
</>
)
} else if (profileMinted && isOwned === false && !isEligible) {
if (badgeForMint.airdrop) {
return (
<>
<SvgIcon sx={{ color: "primary.main", fontSize: "2.4rem" }} component={WarningSvg} inheritViewBox></SvgIcon>
<Typography sx={{ color: "#FF684B !important", fontSize: ["1.6rem", "1.8rem"], lineHeight: ["2.4rem", "2.8rem"], fontWeight: 500 }}>
This is an airdrop-only badge. Selected account is not eligible.
</Typography>
</>
)
}
return (
<>
<SvgIcon sx={{ color: "primary.main", fontSize: "2.4rem" }} component={WarningSvg} inheritViewBox></SvgIcon>
Expand Down
22 changes: 14 additions & 8 deletions src/services/canvasService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,20 @@ const checkBadgeEligibility = async (provider, walletAddress, badge: any) => {
const eligibility = await badge.validator(provider, walletAddress)
return eligibility
}

// scroll native
if (badge.native) {
if (!badge.baseUrl && !badge.eligibilityCheck) {
return true
}
// third-party badge
if (badge.attesterProxy) {
const data = await scrollRequest(checkBadgeEligibilityURL(badge.baseUrl, walletAddress, badge.badgeContract))
// permissionless
if (!badge.baseUrl && badge.eligibilityCheck) {
const badgeInstance = new ethers.Contract(badge.badgeContract, BadgeABI, provider)
const eligibility = await badgeInstance.isEligible(walletAddress)
return eligibility
}
// backend authorized / airdropped
if (badge.baseUrl) {
const data = await scrollRequest(checkBadgeEligibilityURL(badge.baseUrl, walletAddress, badge.badgeContract), {
timeout: 1e4,
})
// TODO: must return true or false
return data.eligibility ?? false
}
Expand All @@ -273,7 +279,7 @@ const checkBadgeEligibility = async (provider, walletAddress, badge: any) => {
}
}

const mintThirdBadge = async (signer, walletAddress, badgeAddress, attesterProxyAddress, claimBaseUrl) => {
const mintBackendAuthorizedBadge = async (signer, walletAddress, badgeAddress, attesterProxyAddress, claimBaseUrl) => {
const { tx: unsignedTx } = await scrollRequest(claimBadgeURL(claimBaseUrl, walletAddress, badgeAddress))
console.log(unsignedTx, "unsignedTx")
checkDelegatedAttestation(unsignedTx, attesterProxyAddress)
Expand Down Expand Up @@ -359,7 +365,7 @@ const mintBadge = async (provider, walletCurrentAddress, badge) => {
}
// Third Party Badge
if (attesterProxy) {
return await mintThirdBadge(signer, walletCurrentAddress, badgeContract, attesterProxy, baseUrl)
return await mintBackendAuthorizedBadge(signer, walletCurrentAddress, badgeContract, attesterProxy, baseUrl)
}

return await mintPermissionlessBadge(signer, walletCurrentAddress, badgeContract)
Expand Down