diff --git a/app/.env-example.env b/app/.env-example.env
index 568172aa73..cfdca320c0 100644
--- a/app/.env-example.env
+++ b/app/.env-example.env
@@ -93,5 +93,10 @@ NEXT_PUBLIC_ONBOARD_RESET_INDEX=1
NEXT_PUBLIC_GA_ID=id
# Env vars for the scroll campaign
-NEXT_PUBLIC_SCROLL_BADGE_PROVIDER_INFO='[{"badgeContractAddress":"0x...","title":"Passport Developer","providers":[{"level":1,"name":"DeveloperList#PassportCommiterLevel1#7f421a19","image":"/assets/img1.svg"}]}]'
+# The sample below applies for the following rulesets (defined in adjango admin):
+# - {"name": "ContributorPassport10", "condition": {"OR": [{"repository_commit_count": {"threshold": 10, "repository": "https://github.com/passportxyz/passport-scorer"}}, {"repository_commit_count": {"threshold": 10, "repository": "https://github.com/passportxyz/passport"}}]}}
+# - {"name": "ContributorPassport30", "condition": {"OR": [{"repository_commit_count": {"threshold": 30, "repository": "https://github.com/passportxyz/passport-scorer"}}, {"repository_commit_count": {"threshold": 30, "repository": "https://github.com/passportxyz/passport"}}]}}
+# - {"name": "ContributorPassport20", "condition": {"OR": [{"repository_commit_count": {"threshold": 20, "repository": "https://github.com/passportxyz/passport-scorer"}}, {"repository_commit_count": {"threshold": 20, "repository": "https://github.com/passportxyz/passport"}}]}}
+
+NEXT_PUBLIC_SCROLL_BADGE_PROVIDER_INFO='[{"badgeContractAddress":"0x71A848A38fFCcA5c7A431F2BB411Ab632Fa0c456","title":"Passport Developer","providers":[{"level":100,"name":"DeveloperList#ContributorPassport10#b1933500","image":"assets/scrollBadgeLevel1.svg"},{"level":200,"name":"DeveloperList#ContributorPassport20#a4d87d4e","image":"assets/scrollBadgeLevel2.svg"},{"level":300,"name":"DeveloperList#ContributorPassport30#4f1f3558","image":"assets/scrollDevZKRollup3.svg"}]},{"badgeContractAddress":"0x71A848A38fFCcA5c7A431F2BB411Ab632Fa0c456","title":"Passport Developer 2","providers":[{"level":100,"name":"DeveloperList#ContributorPassport10#b1933500","image":"assets/scrollBadgeLevel1.svg"},{"level":200,"name":"DeveloperList#ContributorPassport20#a4d87d4e","image":"assets/scrollBadgeLevel2.svg"},{"level":300,"name":"DeveloperList#ContributorPassport30#4f1f3558","image":"assets/scrollDevZKRollup3.svg"}]}]'
NEXT_PUBLIC_SCROLL_CAMPAIGN_CHAIN_ID=
diff --git a/app/__tests__/components/ScrollCampaign.test.tsx b/app/__tests__/components/ScrollCampaign.test.tsx
index 90a57c9eeb..62d1fd942e 100644
--- a/app/__tests__/components/ScrollCampaign.test.tsx
+++ b/app/__tests__/components/ScrollCampaign.test.tsx
@@ -5,7 +5,7 @@ import { MemoryRouter } from "react-router-dom";
import { makeTestCeramicContext, renderWithContext } from "../../__test-fixtures__/contextTestHelpers";
import { CeramicContextState } from "../../context/ceramicContext";
import { AppRoutes } from "../../pages";
-import { ScrollStepsBar } from "../../components/ScrollCampaign";
+// import { ScrollStepsBar } from "../../components/ScrollCampaign";
import { useParams } from "react-router-dom";
import { CredentialResponseBody } from "@gitcoin/passport-types";
import { googleStampFixture } from "../../__test-fixtures__/databaseStorageFixtures";
@@ -140,47 +140,47 @@ describe("Landing page tests", () => {
});
});
-describe("Component tests", () => {
- beforeEach(() => {
- jest.restoreAllMocks();
- jest.clearAllMocks();
- });
- it("shows step 0 correctly", () => {
- (useParams as jest.Mock).mockReturnValue({ campaignId: "scroll-developer", step: "0" });
+// describe("Component tests", () => {
+// beforeEach(() => {
+// jest.restoreAllMocks();
+// jest.clearAllMocks();
+// });
+// it("shows step 0 correctly", () => {
+// (useParams as jest.Mock).mockReturnValue({ campaignId: "scroll-developer", step: "0" });
- render( );
+// render( );
- const connectWalletStep = screen.getByText("Connect Wallet");
- expect(connectWalletStep).toBeInTheDocument();
- expect(connectWalletStep).not.toHaveClass("brightness-50");
+// const connectWalletStep = screen.getByText("Connect Wallet");
+// expect(connectWalletStep).toBeInTheDocument();
+// expect(connectWalletStep).not.toHaveClass("brightness-50");
- const githubStep = screen.getByText("Connect to Github");
- expect(githubStep).toBeInTheDocument();
- expect(githubStep).toHaveClass("brightness-50");
+// const githubStep = screen.getByText("Connect to Github");
+// expect(githubStep).toBeInTheDocument();
+// expect(githubStep).toHaveClass("brightness-50");
- const mintStep = screen.getByText("Mint Badge");
- expect(mintStep).toBeInTheDocument();
- expect(mintStep).toHaveClass("brightness-50");
- });
+// const mintStep = screen.getByText("Mint Badge");
+// expect(mintStep).toBeInTheDocument();
+// expect(mintStep).toHaveClass("brightness-50");
+// });
- it("shows step 1 correctly", () => {
- (useParams as jest.Mock).mockReturnValue({ campaignId: "scroll-developer", step: "1" });
+// it("shows step 1 correctly", () => {
+// (useParams as jest.Mock).mockReturnValue({ campaignId: "scroll-developer", step: "1" });
- render( );
+// render( );
- const connectWalletStep = screen.getByText("Connect Wallet");
- expect(connectWalletStep).toBeInTheDocument();
- expect(connectWalletStep).toHaveClass("brightness-50");
+// const connectWalletStep = screen.getByText("Connect Wallet");
+// expect(connectWalletStep).toBeInTheDocument();
+// expect(connectWalletStep).toHaveClass("brightness-50");
- const githubStep = screen.getByText("Connect to Github");
- expect(githubStep).toBeInTheDocument();
- expect(githubStep).not.toHaveClass("brightness-50");
+// const githubStep = screen.getByText("Connect to Github");
+// expect(githubStep).toBeInTheDocument();
+// expect(githubStep).not.toHaveClass("brightness-50");
- const mintStep = screen.getByText("Mint Badge");
- expect(mintStep).toBeInTheDocument();
- expect(mintStep).toHaveClass("brightness-50");
- });
-});
+// const mintStep = screen.getByText("Mint Badge");
+// expect(mintStep).toBeInTheDocument();
+// expect(mintStep).toHaveClass("brightness-50");
+// });
+// });
describe("Github Connect page tests", () => {
beforeEach(async () => {
diff --git a/app/components/ScrollCampaign.tsx b/app/components/ScrollCampaign.tsx
index b0ff49638b..df3f4c2a92 100644
--- a/app/components/ScrollCampaign.tsx
+++ b/app/components/ScrollCampaign.tsx
@@ -1,5 +1,4 @@
-import React, { useEffect, useContext, useMemo, useState, useCallback } from "react";
-import { useParams } from "react-router-dom";
+import React, { useEffect, useContext, useMemo, useState } from "react";
import NotFound from "../pages/NotFound";
import PageRoot from "./PageRoot";
import { AccountCenter } from "./AccountCenter";
@@ -9,177 +8,59 @@ import { LoadButton } from "./LoadButton";
import {
useNextCampaignStep,
useNavigateToRootStep,
- useNavigateToLastStep,
useNavigateToGithubConnectStep,
} from "../hooks/useNextCampaignStep";
import { useScrollBadge } from "../hooks/useScrollBadge";
import { useDatastoreConnectionContext } from "../context/datastoreConnectionContext";
import { CeramicContext } from "../context/ceramicContext";
-import { waitForRedirect } from "../context/stampClaimingContext";
-
-import { useWalletStore } from "../context/walletStore";
-
-import { CUSTOM_PLATFORM_TYPE_INFO } from "../config/platformMap";
-import { EasPayload, Passport, PROVIDER_ID, Stamp, VerifiableCredential } from "@gitcoin/passport-types";
-import { fetchVerifiableCredential } from "@gitcoin/passport-identity";
-import { IAM_SIGNATURE_TYPE, iamUrl } from "../config/stamp_config";
-import { createSignedPayload, generateUID } from "../utils/helpers";
-import { GitHubIcon } from "./WelcomeFooter";
-import { datadogLogs } from "@datadog/browser-logs";
+import { EasPayload, PROVIDER_ID, Passport, Stamp } from "@gitcoin/passport-types";
import { useSetCustomizationKey } from "../hooks/useCustomization";
-import { LoadingBarSection, LoadingBarSectionProps } from "./LoadingBar";
-import {
- scrollCampaignBadgeProviders,
- scrollCampaignBadgeProviderInfo,
- scrollCampaignChain,
-} from "../config/scroll_campaign";
+import { badgeContractInfo, scrollCampaignBadgeProviders, scrollCampaignChain } from "../config/scroll_campaign";
+
+import { useMessage } from "../hooks/useMessage";
+import { ScrollFooter, ScrollHeader } from "./scroll/ScrollLayout";
+import { ScrollCampaignPage } from "./scroll/ScrollCampaignPage";
+import { ScrollConnectGithub } from "./scroll/ScrollConnectGithub";
+import { ScrollMintBadge } from "./scroll/ScrollMintPage";
import { useAttestation } from "../hooks/useAttestation";
+import { iamUrl } from "../config/stamp_config";
+import { useScrollStampsStore } from "../context/scrollCampaignStore";
import { jsonRequest } from "../utils/AttestationProvider";
-import { useMessage } from "../hooks/useMessage";
-
-const SCROLL_STEP_NAMES = ["Connect Wallet", "Connect to Github", "Mint Badge"];
-
-export const ScrollStepsBar = ({
- className,
- highLightCurrentStep = true,
-}: {
- className?: string;
- highLightCurrentStep?: boolean;
-}) => {
- const { step } = useParams();
- return (
-
- {SCROLL_STEP_NAMES.map((stepName, index) => (
-
-
- {index + 1}
-
- {stepName}
-
- ))}
-
- );
-};
-
-const ScrollHeader = ({ className }: { className?: string }) => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-const ScrollFooter = ({ className }: { className?: string }) => {
- return (
-
-
powered by
-
-
Passport
-
- );
-};
-
-const ScrollCampaignPageRoot = ({ children }: { children: React.ReactNode }) => {
- const { isConnected } = useWeb3ModalAccount();
- return (
-
- {isConnected && }
-
- {children}
-
-
- );
-};
-
-const BackgroundImage = ({ fadeBackgroundImage }: { fadeBackgroundImage?: boolean }) => (
-
-
-
-);
-
-const ScrollCampaignPage = ({
- children,
- fadeBackgroundImage,
- emblemSrc,
-}: {
- children: React.ReactNode;
- fadeBackgroundImage?: boolean;
- emblemSrc?: string;
-}) => {
- return (
-
-
- {emblemSrc && (
-
-
-
- )}
-
-
-
- );
+interface Provider {
+ name: PROVIDER_ID;
+ image: string;
+ level: number;
+}
+
+interface BadgeContract {
+ badgeContractAddress: string;
+ title: string;
+ providers: Provider[];
+}
+
+export interface ProviderWithTitle extends Provider {
+ title: string;
+}
+
+interface TopBadges {
+ title: string;
+ level: number;
+ image: string;
+}
+
+const getTopBadgesInfo = (): TopBadges[] => {
+ return badgeContractInfo.map((item) => {
+ const highestLevelProvider = item.providers.reduce((prev, current) =>
+ prev.level > current.level ? prev : current
+ );
+
+ return {
+ title: item.title,
+ level: highestLevelProvider.level,
+ image: highestLevelProvider.image,
+ };
+ });
};
const ScrollLogin = () => {
@@ -225,166 +106,24 @@ const ScrollLogin = () => {
);
};
-const ScrollConnectGithub = () => {
- const goToNextStep = useNextCampaignStep();
- const goToLastStep = useNavigateToLastStep();
- const { isConnected } = useWeb3ModalAccount();
- const { did, checkSessionIsValid } = useDatastoreConnectionContext();
- const { userDid, database } = useContext(CeramicContext);
- const goToLoginStep = useNavigateToRootStep();
- const address = useWalletStore((state) => state.address);
- const [noCredentialReceived, setNoCredentialReceived] = useState(false);
- const [msg, setMsg] = useState("Verifying existing badges on chain ... ");
- const [isVerificationRunning, setIsVerificationRunning] = useState(false);
- const { failure } = useMessage();
-
- const { areBadgesLoading, hasAtLeastOneBadge } = useScrollBadge(address);
-
- useEffect(() => {
- // If the user already has on chain badge redirect to final step
- if (!areBadgesLoading && hasAtLeastOneBadge) {
- goToLastStep();
- } else {
- setMsg(undefined);
- }
- }, [areBadgesLoading, hasAtLeastOneBadge, goToLastStep]);
-
- const signInWithGithub = useCallback(async () => {
- setIsVerificationRunning(true);
- try {
- if (did) {
- const customGithubPlatform = new CUSTOM_PLATFORM_TYPE_INFO.DEVEL.platformClass(
- // @ts-ignore
- CUSTOM_PLATFORM_TYPE_INFO.DEVEL.platformParams
- );
- setMsg("Connecting to Github ...");
- const state = `${customGithubPlatform.path}-` + generateUID(10);
- const providerPayload = (await customGithubPlatform.getProviderPayload({
- state,
- window,
- screen,
- userDid,
- callbackUrl: window.location.origin,
- selectedProviders: scrollCampaignBadgeProviders,
- waitForRedirect,
- })) as {
- [k: string]: string;
- };
-
- if (!checkSessionIsValid()) {
- console.error(
- "It seems that the session is not valid any more (it might have timed out). Going back to login screen."
- );
- goToLoginStep();
- }
-
- setMsg("Please wait, we are checking your eligibility ...");
- const verifyCredentialsResponse = await fetchVerifiableCredential(
- iamUrl,
- {
- type: customGithubPlatform.platformId,
- types: scrollCampaignBadgeProviders,
- version: "0.0.0",
- address: address || "",
- proofs: providerPayload,
- signatureType: IAM_SIGNATURE_TYPE,
- },
- (data: any) => createSignedPayload(did, data)
- );
-
- setMsg(undefined);
- const verifiedCredentials =
- scrollCampaignBadgeProviders.length > 0
- ? verifyCredentialsResponse.credentials?.reduce((acc: VerifiableCredential[], cred: any) => {
- if (!cred.error) {
- acc.push(cred.credential); // Accumulate only valid credentials
- }
- return acc;
- }, [] as VerifiableCredential[]) || []
- : [];
-
- if (verifiedCredentials.length > 0 && database) {
- const saveResult = await database.addStamps(
- verifiedCredentials.map(
- (credential): Stamp => ({ credential, provider: credential.credentialSubject.provider as PROVIDER_ID })
- )
- );
-
- if (saveResult.status !== "Success") {
- datadogLogs.logger.error("Error saving stamps to database: ", { address, saveResult });
- failure({
- title: "Error",
- message: "An unexpected error occurred while saving the credentials",
- });
- }
-
- goToNextStep();
- } else {
- setNoCredentialReceived(true);
+export const getHighestEarnedBadgeProviderInfo = (contractAddress: string, level: number) => {
+ const badgeContract = badgeContractInfo.find((contract) => contract.badgeContractAddress === contractAddress);
+ if (badgeContract) {
+ return badgeContract.providers.reduce(
+ (acc, provider) => {
+ if (provider.level <= level && provider.level > acc.level) {
+ acc = { title: badgeContract.title, ...provider };
}
+ return acc;
+ },
+ {
+ title: "",
+ name: "No Provider" as PROVIDER_ID,
+ image: "",
+ level: 0,
}
- } finally {
- setIsVerificationRunning(false);
- }
- }, [did, address, checkSessionIsValid, goToLoginStep, goToNextStep, userDid]);
-
- const body = noCredentialReceived ? (
- <>
- We're sorry!
- You do not qualify because you do not have the minimum 10 contributions needed.
- >
- ) : (
- <>
- Connect to Github
-
- Passport is privacy preserving and verifies you have 1 or more commits to the following Repos located here.
- Click below and obtain the specific developer credentials
-
-
-
- {msg ? msg : "Connect to Github"}
-
-
- >
- );
- return (
-
- {isConnected && }
-
-
-
-
-
{body}
-
-
- {/* {!noCredentialReceived && (
-
- )} */}
-
-
-
-
-
-
-
-
- );
+ );
+ }
};
const ScrollMintedBadge = () => {
@@ -393,6 +132,9 @@ const ScrollMintedBadge = () => {
const { isConnected, address } = useWeb3ModalAccount();
const { did, dbAccessToken } = useDatastoreConnectionContext();
const { badges, areBadgesLoading, errors, hasAtLeastOneBadge } = useScrollBadge(address);
+ const { database } = useContext(CeramicContext);
+ const { getNonce, issueAttestation, needToSwitchChain } = useAttestation({ chain: scrollCampaignChain });
+ const [syncingToChain, setSyncingToChain] = useState(false);
const { failure } = useMessage();
@@ -427,21 +169,28 @@ const ScrollMintedBadge = () => {
You already minted available badges!
-
Here are all your badges
{areBadgesLoading ? (
Loading badges...
) : badges.length === 0 ? (
No badges found.
) : (
-
- {badges.map((badge, index) =>
- badge.hasBadge ? (
-
-
-
Level: {badge.badgeLevel}
+
+ {badges.map((badge, index) => {
+ const badgeProviderInfo = getHighestEarnedBadgeProviderInfo(badge.contract, badge.badgeLevel);
+ return badge.hasBadge && badgeProviderInfo ? (
+
+
+
{badgeProviderInfo.title}
+
Level: {badge.badgeLevel}
- ) : null
- )}
+ ) : (
+ <>>
+ );
+ })}
)}
{
);
};
-const ScrollLoadingBarSection = (props: LoadingBarSectionProps) => (
-
-);
+export const getEarnedBadges = (badgeStamps: Stamp[]): ProviderWithTitle[] => {
+ if (badgeStamps.length === 0) {
+ return [];
+ }
+ return badgeContractInfo.map((contract) => {
+ const relevantStamps = badgeStamps.filter((stamp) =>
+ contract.providers.some(({ name }) => name === stamp.provider)
+ );
+
+ if (relevantStamps.length === 0) {
+ return {
+ title: contract.title,
+ name: "No Provider" as PROVIDER_ID,
+ image: "",
+ level: 0,
+ };
+ }
-const ScrollMintBadge = () => {
- const { failure } = useMessage();
+ const highestLevelProvider = relevantStamps.reduce(
+ (highest, stamp) => {
+ const provider = contract.providers.find(({ name }) => name === stamp.provider);
+ console.log({ provider, highest });
+ if (provider && provider.level > highest.level) {
+ return provider;
+ }
+ return highest;
+ },
+ { level: -1, name: "No Provider" as PROVIDER_ID, image: "" }
+ );
+ console.log({ highestLevelProvider });
+
+ return {
+ title: contract.title,
+ ...highestLevelProvider,
+ };
+ });
+};
+
+export const ScrollCampaign = ({ step }: { step: number }) => {
+ const setCustomizationKey = useSetCustomizationKey();
+ const goToLoginStep = useNavigateToRootStep();
+ const goToGithubConnectStep = useNavigateToGithubConnectStep();
+ const { isConnected, address } = useWeb3ModalAccount();
+ const { did, dbAccessToken } = useDatastoreConnectionContext();
+ const { badges, areBadgesLoading, errors, hasAtLeastOneBadge } = useScrollBadge(address);
const { database } = useContext(CeramicContext);
- const address = useWalletStore((state) => state.address);
const { getNonce, issueAttestation, needToSwitchChain } = useAttestation({ chain: scrollCampaignChain });
const [syncingToChain, setSyncingToChain] = useState(false);
+ const { credentials } = useScrollStampsStore();
+ const { failure } = useMessage();
- const [passport, setPassport] = useState(undefined);
+ useEffect(() => {
+ setCustomizationKey("scroll");
+ }, [setCustomizationKey]);
useEffect(() => {
- (async () => {
- if (database) {
- const passportLoadResponse = await database.getPassport();
- if (passportLoadResponse.status === "Success") {
- setPassport(passportLoadResponse.passport);
- } else {
- failure({
- title: "Error",
- message: "An unexpected error occurred while loading your Passport.",
- });
- }
- }
- })();
- }, [database]);
+ if ((!dbAccessToken || !did || !database) && step > 0) {
+ console.log("Access token or did are not present. Going back to login step!");
+ goToLoginStep();
+ }
+ }, [dbAccessToken, did, step, goToLoginStep]);
+
+ const [passport, setPassport] = useState(undefined);
const badgeStamps = useMemo(
() => (passport ? passport.stamps.filter(({ provider }) => scrollCampaignBadgeProviders.includes(provider)) : []),
[passport]
);
- const loading = !passport;
-
const deduplicatedBadgeStamps = useMemo(
// TODO Deduplicate by seeing if in burnedHashes but not user's hashes
() => badgeStamps.filter(({ provider }) => true),
[badgeStamps]
);
- const hasDeduplicatedCredentials = badgeStamps.length > deduplicatedBadgeStamps.length;
-
- const highestLevelBadgeStamps = useMemo(
- () =>
- Object.values(
- deduplicatedBadgeStamps.reduce(
- (acc, credential) => {
- const { contractAddress, level } = scrollCampaignBadgeProviderInfo[credential.provider];
- if (!acc[contractAddress] || level > acc[contractAddress].level) {
- acc[contractAddress] = { level, credential };
- }
- return acc;
- },
- {} as Record
- )
- ).map(({ credential }) => credential),
- [badgeStamps, deduplicatedBadgeStamps]
- );
+ const hasBadge = deduplicatedBadgeStamps.length > 0;
+ const hasMultipleBadges = deduplicatedBadgeStamps.length > 1;
- const hasBadge = highestLevelBadgeStamps.length > 0;
- const hasMultipleBadges = highestLevelBadgeStamps.length > 1;
+ const loading = !passport;
const onMint = async () => {
try {
@@ -540,7 +305,7 @@ const ScrollMintBadge = () => {
const url = `${iamUrl}v0.0.0/scroll/dev`;
const { data }: { data: EasPayload } = await jsonRequest(url, {
recipient: address || "",
- credentials: deduplicatedBadgeStamps.map(({ credential }) => credential),
+ credentials: credentials, // deduplicatedBadgeStamps.map(({ credential }) => credential),
chainIdHex: scrollCampaignChain?.id,
nonce,
});
@@ -565,79 +330,12 @@ const ScrollMintBadge = () => {
setSyncingToChain(false);
};
- return (
-
-
- {hasBadge ? "Congratulations!" : "We're sorry!"}
-
-
- {hasBadge ? (
-
- You qualify for {highestLevelBadgeStamps.length} badge{hasMultipleBadges ? "s" : ""}. Mint your badge
- {hasMultipleBadges ? "s" : ""} and get a chance to work with us.
- {hasDeduplicatedCredentials
- ? " (Some badge credentials could not be validated because they have already been claimed on another address.)"
- : ""}
-
- ) : hasDeduplicatedCredentials ? (
- "Your badge credentials have already been claimed with another address."
- ) : (
- "You don't qualify for any badges."
- )}
-
-
- {hasBadge && (
-
-
-
- {syncingToChain ? "Minting..." : "Mint Badge"}
-
-
- {needToSwitchChain && (
-
- You will be prompted to switch to the Scroll chain, and then to submit a transaction.
-
- )}
-
- )}
-
- );
-};
-
-export const ScrollCampaign = ({ step }: { step: number }) => {
- const { did, dbAccessToken } = useDatastoreConnectionContext();
- const { database } = useContext(CeramicContext);
- const goToLoginStep = useNavigateToRootStep();
- const setCustomizationKey = useSetCustomizationKey();
-
- useEffect(() => {
- setCustomizationKey("scroll");
- }, [setCustomizationKey]);
-
- useEffect(() => {
- if ((!dbAccessToken || !did || !database) && step > 0) {
- console.log("Access token or did are not present. Going back to login step!");
- goToLoginStep();
- }
- }, [dbAccessToken, did, step, goToLoginStep]);
-
if (step === 0) {
return ;
} else if (step === 1) {
return ;
} else if (step === 2) {
- return ;
+ return ;
} else if (step === 3) {
return ;
}
diff --git a/app/components/scroll/ScrollCampaignPage.tsx b/app/components/scroll/ScrollCampaignPage.tsx
new file mode 100644
index 0000000000..e76648e957
--- /dev/null
+++ b/app/components/scroll/ScrollCampaignPage.tsx
@@ -0,0 +1,46 @@
+import { ProviderWithTitle, getHighestEarnedBadgeProviderInfo } from "../ScrollCampaign";
+import { BackgroundImage, ScrollCampaignPageRoot, ScrollStepsBar } from "./ScrollLayout";
+
+export const ScrollCampaignPage = ({
+ children,
+ fadeBackgroundImage,
+ earnedBadges,
+}: {
+ children: React.ReactNode;
+ fadeBackgroundImage?: boolean;
+ earnedBadges?: ProviderWithTitle[];
+}) => {
+ return (
+
+
+
+
+ {earnedBadges &&
+ earnedBadges.map((badge, index) => (
+
+
+
+
{badge.title}
+
Level: {badge.level}
+
+
+ ))}
+
+
+
+
+
+ );
+};
diff --git a/app/components/scroll/ScrollConnectGithub.tsx b/app/components/scroll/ScrollConnectGithub.tsx
new file mode 100644
index 0000000000..c582df519b
--- /dev/null
+++ b/app/components/scroll/ScrollConnectGithub.tsx
@@ -0,0 +1,160 @@
+import { useWeb3ModalAccount } from "@web3modal/ethers/react";
+import { useNavigateToLastStep, useNavigateToRootStep, useNextCampaignStep } from "../../hooks/useNextCampaignStep";
+import { useDatastoreConnectionContext } from "../../context/datastoreConnectionContext";
+import { useCallback, useContext, useEffect, useState } from "react";
+import { CeramicContext } from "../../context/ceramicContext";
+import { useWalletStore } from "../../context/walletStore";
+import { useMessage } from "../../hooks/useMessage";
+import { useScrollBadge } from "../../hooks/useScrollBadge";
+import { CUSTOM_PLATFORM_TYPE_INFO } from "../../config/platformMap";
+import { createSignedPayload, generateUID } from "../../utils/helpers";
+import { scrollCampaignBadgeProviders } from "../../config/scroll_campaign";
+import { waitForRedirect } from "../../context/stampClaimingContext";
+import { fetchVerifiableCredential } from "@gitcoin/passport-identity";
+import { IAM_SIGNATURE_TYPE, iamUrl } from "../../config/stamp_config";
+import { PROVIDER_ID, Stamp, VerifiableCredential } from "@gitcoin/passport-types";
+import { datadogLogs } from "@datadog/browser-logs";
+import { LoadButton } from "../LoadButton";
+import { GitHubIcon } from "../WelcomeFooter";
+import { ScrollCampaignPage } from "./ScrollCampaignPage";
+import { useScrollStampsStore } from "../../context/scrollCampaignStore";
+
+export const ScrollConnectGithub = () => {
+ const goToNextStep = useNextCampaignStep();
+ const goToLastStep = useNavigateToLastStep();
+ const { isConnected } = useWeb3ModalAccount();
+ const { did, checkSessionIsValid } = useDatastoreConnectionContext();
+ const { userDid, database } = useContext(CeramicContext);
+ const goToLoginStep = useNavigateToRootStep();
+ const address = useWalletStore((state) => state.address);
+ const [noCredentialReceived, setNoCredentialReceived] = useState(false);
+ const [msg, setMsg] = useState("Verifying existing badges on chain ... ");
+ const [isVerificationRunning, setIsVerificationRunning] = useState(false);
+ const { failure } = useMessage();
+ const { setCredentials } = useScrollStampsStore();
+
+ const { areBadgesLoading, hasAtLeastOneBadge } = useScrollBadge(address);
+
+ useEffect(() => {
+ // If the user already has on chain badge redirect to final step
+ if (!areBadgesLoading && hasAtLeastOneBadge) {
+ goToLastStep();
+ } else {
+ setMsg(undefined);
+ }
+ }, [areBadgesLoading, hasAtLeastOneBadge, goToLastStep]);
+
+ const signInWithGithub = useCallback(async () => {
+ setIsVerificationRunning(true);
+ try {
+ if (did) {
+ const customGithubPlatform = new CUSTOM_PLATFORM_TYPE_INFO.DEVEL.platformClass(
+ // @ts-ignore
+ CUSTOM_PLATFORM_TYPE_INFO.DEVEL.platformParams
+ );
+ setMsg("Connecting to Github ...");
+ const state = `${customGithubPlatform.path}-` + generateUID(10);
+ const providerPayload = (await customGithubPlatform.getProviderPayload({
+ state,
+ window,
+ screen,
+ userDid,
+ callbackUrl: window.location.origin,
+ selectedProviders: scrollCampaignBadgeProviders,
+ waitForRedirect,
+ })) as {
+ [k: string]: string;
+ };
+
+ if (!checkSessionIsValid()) {
+ console.error(
+ "It seems that the session is not valid any more (it might have timed out). Going back to login screen."
+ );
+ goToLoginStep();
+ }
+
+ setMsg("Please wait, we are checking your eligibility ...");
+ const verifyCredentialsResponse = await fetchVerifiableCredential(
+ iamUrl,
+ {
+ type: customGithubPlatform.platformId,
+ types: scrollCampaignBadgeProviders,
+ version: "0.0.0",
+ address: address || "",
+ proofs: providerPayload,
+ signatureType: IAM_SIGNATURE_TYPE,
+ },
+ (data: any) => createSignedPayload(did, data)
+ );
+
+ setMsg(undefined);
+ const verifiedCredentials =
+ scrollCampaignBadgeProviders.length > 0
+ ? verifyCredentialsResponse.credentials?.reduce((acc: VerifiableCredential[], cred: any) => {
+ if (!cred.error) {
+ acc.push(cred.credential); // Accumulate only valid credentials
+ }
+ return acc;
+ }, [] as VerifiableCredential[]) || []
+ : [];
+
+ if (verifiedCredentials.length > 0 && database) {
+ const saveResult = await database.addStamps(
+ verifiedCredentials.map(
+ (credential): Stamp => ({ credential, provider: credential.credentialSubject.provider as PROVIDER_ID })
+ )
+ );
+
+ if (saveResult.status !== "Success") {
+ datadogLogs.logger.error("Error saving stamps to database: ", { address, saveResult });
+ failure({
+ title: "Error",
+ message: "An unexpected error occurred while saving the credentials",
+ });
+ }
+
+ setCredentials(verifiedCredentials);
+ goToNextStep();
+ } else {
+ setNoCredentialReceived(true);
+ }
+ }
+ } finally {
+ setIsVerificationRunning(false);
+ }
+ }, [did, address, checkSessionIsValid, goToLoginStep, goToNextStep, userDid]);
+
+ const body = noCredentialReceived ? (
+ <>
+ We're sorry!
+ You do not qualify because you do not have the minimum 10 contributions needed.
+ >
+ ) : (
+ <>
+ Connect to Github
+
+ Passport is privacy preserving and verifies you have 1 or more commits to the following Repos located here.
+ Click below and obtain the specific developer credentials
+
+
+
+ {msg ? msg : "Connect to Github"}
+
+
+ >
+ );
+ return (
+
+ {body}
+
+
+ );
+};
diff --git a/app/components/scroll/ScrollLayout.tsx b/app/components/scroll/ScrollLayout.tsx
new file mode 100644
index 0000000000..a53cf818e0
--- /dev/null
+++ b/app/components/scroll/ScrollLayout.tsx
@@ -0,0 +1,117 @@
+import { useWeb3ModalAccount } from "@web3modal/ethers/react";
+import PageRoot from "../PageRoot";
+import { AccountCenter } from "../AccountCenter";
+import { useParams } from "react-router-dom";
+
+export const ScrollHeader = ({ className }: { className?: string }) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const ScrollFooter = ({ className }: { className?: string }) => {
+ return (
+
+
powered by
+
+
Passport
+
+ );
+};
+
+export const ScrollCampaignPageRoot = ({ children }: { children: React.ReactNode }) => {
+ const { isConnected } = useWeb3ModalAccount();
+ return (
+
+ {isConnected && }
+
+ {children}
+
+
+ );
+};
+
+export const BackgroundImage = ({ fadeBackgroundImage }: { fadeBackgroundImage?: boolean }) => (
+
+
+
+);
+
+const SCROLL_STEP_NAMES = ["Connect Wallet", "Connect to Github", "Mint Badge"];
+export const ScrollStepsBar = ({
+ className,
+ highLightCurrentStep = true,
+}: {
+ className?: string;
+ highLightCurrentStep?: boolean;
+}) => {
+ const { step } = useParams();
+ return (
+
+ {SCROLL_STEP_NAMES.map((stepName, index) => (
+
+
+ {index + 1}
+
+ {stepName}
+
+ ))}
+
+ );
+};
diff --git a/app/components/scroll/ScrollMintPage.tsx b/app/components/scroll/ScrollMintPage.tsx
new file mode 100644
index 0000000000..1f5cd2b330
--- /dev/null
+++ b/app/components/scroll/ScrollMintPage.tsx
@@ -0,0 +1,170 @@
+import { useContext, useEffect, useMemo, useState } from "react";
+import { useMessage } from "../../hooks/useMessage";
+import { useWalletStore } from "../../context/walletStore";
+import { CeramicContext } from "../../context/ceramicContext";
+import { useAttestation } from "../../hooks/useAttestation";
+import { EasPayload, Passport, Stamp } from "@gitcoin/passport-types";
+import {
+ scrollCampaignBadgeProviderInfo,
+ scrollCampaignBadgeProviders,
+ scrollCampaignChain,
+} from "../../config/scroll_campaign";
+import { getEarnedBadges } from "../ScrollCampaign";
+import { iamUrl } from "../../config/stamp_config";
+import { jsonRequest } from "../../utils/AttestationProvider";
+import { ScrollCampaignPage } from "./ScrollCampaignPage";
+import { LoadingBarSection, LoadingBarSectionProps } from "../LoadingBar";
+import { LoadButton } from "../LoadButton";
+
+export const ScrollMintBadge = ({ onMintBadge }: { onMintBadge: () => Promise }) => {
+ const { failure } = useMessage();
+ const { database } = useContext(CeramicContext);
+ const address = useWalletStore((state) => state.address);
+ const { getNonce, issueAttestation, needToSwitchChain } = useAttestation({ chain: scrollCampaignChain });
+ const [syncingToChain, setSyncingToChain] = useState(false);
+
+ const [passport, setPassport] = useState(undefined);
+
+ useEffect(() => {
+ (async () => {
+ if (database) {
+ const passportLoadResponse = await database.getPassport();
+ if (passportLoadResponse.status === "Success") {
+ setPassport(passportLoadResponse.passport);
+ } else {
+ failure({
+ title: "Error",
+ message: "An unexpected error occurred while loading your Passport.",
+ });
+ }
+ }
+ })();
+ }, [database]);
+
+ const badgeStamps = useMemo(
+ () => (passport ? passport.stamps.filter(({ provider }) => scrollCampaignBadgeProviders.includes(provider)) : []),
+ [passport]
+ );
+
+ const loading = !passport;
+
+ const deduplicatedBadgeStamps = useMemo(
+ // TODO Deduplicate by seeing if in burnedHashes but not user's hashes
+ () => badgeStamps.filter(({ provider }) => true),
+ [badgeStamps]
+ );
+
+ const hasDeduplicatedCredentials = badgeStamps.length > deduplicatedBadgeStamps.length;
+
+ const highestLevelBadgeStamps = useMemo(
+ () =>
+ Object.values(
+ deduplicatedBadgeStamps.reduce(
+ (acc, credential) => {
+ const { contractAddress, level } = scrollCampaignBadgeProviderInfo[credential.provider];
+ if (!acc[contractAddress] || level > acc[contractAddress].level) {
+ acc[contractAddress] = { level, credential };
+ }
+ return acc;
+ },
+ {} as Record
+ )
+ ).map(({ credential }) => credential),
+ [badgeStamps, deduplicatedBadgeStamps]
+ );
+
+ const earnedBadges = getEarnedBadges(badgeStamps);
+
+ const hasBadge = deduplicatedBadgeStamps.length > 0;
+ const hasMultipleBadges = deduplicatedBadgeStamps.length > 1;
+
+ const onMint = async () => {
+ try {
+ setSyncingToChain(true);
+
+ const nonce = await getNonce();
+
+ if (nonce === undefined) {
+ failure({
+ title: "Error",
+ message: "An unexpected error occurred while trying to get the nonce.",
+ });
+ } else {
+ const url = `${iamUrl}v0.0.0/scroll/dev`;
+ const { data }: { data: EasPayload } = await jsonRequest(url, {
+ recipient: address || "",
+ credentials: deduplicatedBadgeStamps.map(({ credential }) => credential),
+ chainIdHex: scrollCampaignChain?.id,
+ nonce,
+ });
+
+ if (data.error) {
+ console.error("error syncing credentials to chain: ", data.error, "nonce:", nonce);
+ failure({
+ title: "Error",
+ message: "An unexpected error occurred while generating attestations.",
+ });
+ } else {
+ issueAttestation({ data });
+ }
+ }
+ } catch (error) {
+ console.error("Error minting badge", error);
+ failure({
+ title: "Error",
+ message: "An unexpected error occurred while trying to bring the data onchain.",
+ });
+ }
+ setSyncingToChain(false);
+ };
+
+ const ScrollLoadingBarSection = (props: LoadingBarSectionProps) => (
+
+ );
+
+ return (
+
+
+ {hasBadge ? "Congratulations!" : "We're sorry!"}
+
+
+ {hasBadge ? (
+
+ You qualify for {deduplicatedBadgeStamps.length} badge{hasMultipleBadges ? "s" : ""}. Mint your badge
+ {hasMultipleBadges ? "s" : ""} and get a chance to work with us.
+ {hasDeduplicatedCredentials
+ ? " (Some badge credentials could not be validated because they have already been claimed on another address.)"
+ : ""}
+
+ ) : hasDeduplicatedCredentials ? (
+ "Your badge credentials have already been claimed with another address."
+ ) : (
+ "You don't qualify for any badges."
+ )}
+
+
+ {hasBadge && (
+
+
+
+ {syncingToChain ? "Minting..." : "Mint Badge"}
+
+
+ {needToSwitchChain && (
+
+ You will be prompted to switch to the Scroll chain, and then to submit a transaction.
+
+ )}
+
+ )}
+
+ );
+};
diff --git a/app/config/scroll_campaign.ts b/app/config/scroll_campaign.ts
index 8f28e7f49b..eb0c82eb6d 100644
--- a/app/config/scroll_campaign.ts
+++ b/app/config/scroll_campaign.ts
@@ -21,7 +21,7 @@ export function loadBadgeProviders(): {
}
}
-const badgeContractInfo = loadBadgeProviders();
+export const badgeContractInfo = loadBadgeProviders();
export const scrollCampaignBadgeProviderInfo = badgeContractInfo.reduce(
(acc, { badgeContractAddress, providers, title }) => {
diff --git a/app/context/scrollCampaignStore.tsx b/app/context/scrollCampaignStore.tsx
new file mode 100644
index 0000000000..1ac0522896
--- /dev/null
+++ b/app/context/scrollCampaignStore.tsx
@@ -0,0 +1,18 @@
+import { VerifiableCredential } from "@gitcoin/passport-types";
+import { create } from "zustand";
+
+/**
+ * Store & manage the credentials relevant for the scroll campaign
+ */
+const scrollStampsStore = create<{
+ credentials: VerifiableCredential[];
+ setCredentials: (credentials: VerifiableCredential[]) => {};
+}>((set) => ({
+ credentials: [] as VerifiableCredential[],
+ setCredentials: async (credentials: VerifiableCredential[]) => {
+ set({ credentials });
+ },
+}));
+
+// Use as hook
+export const useScrollStampsStore = scrollStampsStore;
diff --git a/app/hooks/useAttestation.tsx b/app/hooks/useAttestation.tsx
index d85542bf18..3b57efecb2 100644
--- a/app/hooks/useAttestation.tsx
+++ b/app/hooks/useAttestation.tsx
@@ -24,7 +24,7 @@ const useChainSwitch = ({ chain }: { chain?: Chain }) => {
} catch {
return false;
}
- }, [chain, switchNetwork]);
+ }, [chain, connectedChain, switchNetwork]);
const needToSwitchChain = connectedChain !== chain?.id;
@@ -62,7 +62,7 @@ export const useAttestation = ({ chain }: { chain?: Chain }) => {
const verifierAbi = chain.attestationProvider.verifierAbi();
return new ethers.Contract(verifierAddress, verifierAbi, await ethersProvider.getSigner());
- }, [chain, provider]);
+ }, [chain, failure, needToSwitchChain, provider, switchChain]);
const getNonce = useCallback(async () => {
if (!address) return;
@@ -195,7 +195,7 @@ export const useAttestation = ({ chain }: { chain?: Chain }) => {
}
}
},
- [address, chain?.attestationProvider, chain?.id, refresh, failure, success, provider]
+ [chain, getVerifierContract, success, refresh, address, failure]
);
return useMemo(
diff --git a/app/hooks/useScrollBadge.tsx b/app/hooks/useScrollBadge.tsx
index c56582c5e5..c79f668746 100644
--- a/app/hooks/useScrollBadge.tsx
+++ b/app/hooks/useScrollBadge.tsx
@@ -1,14 +1,18 @@
import { ethers, JsonRpcProvider } from "ethers";
-import { useState, useEffect } from "react";
+import { useState, useEffect, useCallback } from "react";
import PassportScoreScrollBadgeAbi from "../abi/PassportScoreScrollBadge.json";
import { datadogLogs } from "@datadog/browser-logs";
import { scrollCampaignBadgeContractAddresses, scrollCampaignChain } from "../config/scroll_campaign";
+export type BadgeInfo = {
+ contract: string;
+ hasBadge: boolean;
+ badgeLevel: number;
+};
+
export const useScrollBadge = (address: string | undefined) => {
const [areBadgesLoading, setBadgesLoading] = useState(true);
- const [badges, setBadges] = useState<{ contract: string; hasBadge: boolean; badgeLevel: number; badgeUri: string }[]>(
- []
- );
+ const [badges, setBadges] = useState([]);
const [errors, setErrors] = useState<{ [key: string]: string } | {}>({});
useEffect(() => {
@@ -35,98 +39,88 @@ export const useScrollBadge = (address: string | undefined) => {
setBadgesLoading(false);
return;
}
+ }, []);
+
+ const checkBadge = useCallback(async (address: string | undefined) => {
+ try {
+ if (!scrollCampaignChain) {
+ console.log("Scroll Campaign Chain not found");
+ return;
+ }
+ setBadgesLoading(true);
+ const scrollRpcProvider = new JsonRpcProvider(scrollCampaignChain.rpcUrl);
- const checkBadge = async (address: string | undefined) => {
- try {
- if (!scrollCampaignChain) {
- console.log("Scroll Campaign Chain not found");
- return;
- }
- setBadgesLoading(true);
- const scrollRpcProvider = new JsonRpcProvider(scrollCampaignChain.rpcUrl);
+ const badges = await Promise.all(
+ scrollCampaignBadgeContractAddresses.map(async (contractAddress) => {
+ let resultHasBadge: boolean = false;
+ let resultBadgeLevel: number = 0;
- const badges = await Promise.all(
- scrollCampaignBadgeContractAddresses.map(async (contractAddress) => {
- let resultHasBadge = false;
- let resultBadgeLevel = 0;
- let resultBadgeUri = "";
- try {
- console.log(`[Scroll-Campaign] Checking if ${address} has badge level for contract: ${contractAddress}`);
- datadogLogs.logger.info(
- `[Scroll-Campaign] Checking if ${address} has badge level for contract: ${contractAddress}`
- );
- const contract = new ethers.Contract(contractAddress, PassportScoreScrollBadgeAbi.abi, scrollRpcProvider);
- resultHasBadge = await contract.hasBadge(address);
- if (resultHasBadge) {
- // Get badge level
- try {
- console.log(`[Scroll-Campaign] Checking badge level for contract: ${contractAddress}`);
- datadogLogs.logger.info(`[Scroll-Campaign] Checking badge level for contract: ${contractAddress}`);
- resultBadgeLevel = await contract.badgeLevel(address);
- } catch (err) {
- console.error(
- `[Scroll-Campaign] Error checking badge level for contract ${contractAddress} : ${err}`
- );
- datadogLogs.logger.error(
- `[Scroll-Campaign] Error checking badge level for contract ${contractAddress} : ${err}`
- );
- setErrors((prevErrors) => ({
- ...prevErrors,
- [`badge_level_${contractAddress}`]: "Failed to fetch badge level",
- }));
- }
- // Get badge uri
- try {
- console.log("Checking badge uri for contract: ", contractAddress);
- datadogLogs.logger.info(`[Scroll-Campaign] Checking badge uri for contract ${contractAddress}`);
- resultBadgeUri = await contract.badgeLevelImageURIs(resultBadgeLevel);
- } catch (err) {
- console.error("Error getting badge uri for contract", contractAddress, ":", err);
- datadogLogs.logger.error(
- `[Scroll-Campaign] Error getting badge uri for contract ${contractAddress} : ${err}`
- );
- setErrors((prevErrors) => ({
- ...prevErrors,
- [`badge_uri_${contractAddress}`]: "Failed to fetch badge uri",
- }));
- }
+ try {
+ console.log(`[Scroll-Campaign] Checking if ${address} has badge level for contract: ${contractAddress}`);
+ datadogLogs.logger.info(
+ `[Scroll-Campaign] Checking if ${address} has badge level for contract: ${contractAddress}`
+ );
+ const contract = new ethers.Contract(contractAddress, PassportScoreScrollBadgeAbi.abi, scrollRpcProvider);
+ resultHasBadge = await contract.hasBadge(address);
+ if (resultHasBadge) {
+ // Get badge level and other data
+ try {
+ datadogLogs.logger.info(`[Scroll-Campaign] Fetching contract data for: ${contractAddress}`);
+ resultBadgeLevel = Number(await contract.badgeLevel(address));
+ } catch (err) {
+ console.error(`[Scroll-Campaign] Error fetching contract data for ${contractAddress} : ${err}`);
+ datadogLogs.logger.error(
+ `[Scroll-Campaign] Error fetching contract data for ${contractAddress} : ${err}`
+ );
+ setErrors((prevErrors) => ({
+ ...prevErrors,
+ [`contract_data_${contractAddress}`]: "Failed to fetch contract data",
+ }));
}
- } catch (err) {
- console.error("Error checking badge for contract", contractAddress, ":", err);
- datadogLogs.logger.error(
- `[Scroll-Campaign] Error checking if ${address} has badge level for contract: ${contractAddress}. Err : ${err}`
- );
- setErrors((prevErrors) => ({
- ...prevErrors,
- [`badge_${contractAddress}`]: "Failed to fetch badge",
- }));
}
- return {
- contract: contractAddress,
- hasBadge: resultHasBadge,
- badgeLevel: resultBadgeLevel,
- badgeUri: resultBadgeUri,
- };
- })
- );
- setBadges(badges);
- } catch (err) {
- console.error(`[Scroll-Campaign] Error checking badges : ${err}`);
- datadogLogs.logger.error(`[Scroll-Campaign] Error checking badges : ${err}`);
- setErrors((prevErrors) => ({
- ...prevErrors,
- fetch_badges: "Failed to fetch badges",
- }));
- } finally {
- setBadgesLoading(false);
- }
- };
+ } catch (err) {
+ console.error("Error checking badge for contract", contractAddress, ":", err);
+ datadogLogs.logger.error(
+ `[Scroll-Campaign] Error checking if ${address} has badge level for contract: ${contractAddress}. Err : ${err}`
+ );
+ setErrors((prevErrors) => ({
+ ...prevErrors,
+ [`badge_${contractAddress}`]: "Failed to fetch badge",
+ }));
+ }
+ return {
+ contract: contractAddress,
+ hasBadge: resultHasBadge,
+ badgeLevel: resultBadgeLevel,
+ };
+ })
+ );
+ setBadges(badges);
+ } catch (err) {
+ console.error(`[Scroll-Campaign] Error checking badges : ${err}`);
+ datadogLogs.logger.error(`[Scroll-Campaign] Error checking badges : ${err}`);
+ setErrors((prevErrors) => ({
+ ...prevErrors,
+ fetch_badges: "Failed to fetch badges",
+ }));
+ } finally {
+ setBadgesLoading(false);
+ }
+ }, []);
- checkBadge(address);
- }, [address]);
+ useEffect(() => {
+ if (address) {
+ checkBadge(address);
+ }
+ }, [address, checkBadge]);
// Check if user has at least one badge
const hasAtLeastOneBadge = badges.some((badge) => badge.hasBadge);
- return { badges, areBadgesLoading, errors, hasAtLeastOneBadge };
+ return {
+ badges,
+ areBadgesLoading,
+ errors,
+ hasAtLeastOneBadge,
+ };
};
diff --git a/app/public/assets/img1.svg b/app/public/assets/img1.svg
new file mode 100644
index 0000000000..24c6e37c8b
--- /dev/null
+++ b/app/public/assets/img1.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/public/assets/img2.svg b/app/public/assets/img2.svg
new file mode 100644
index 0000000000..12a3338398
--- /dev/null
+++ b/app/public/assets/img2.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/public/assets/img3.svg b/app/public/assets/img3.svg
new file mode 100644
index 0000000000..0565c10d43
--- /dev/null
+++ b/app/public/assets/img3.svg
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iam/.env-example.env b/iam/.env-example.env
index e8a9d6e8a6..d23957de89 100644
--- a/iam/.env-example.env
+++ b/iam/.env-example.env
@@ -79,5 +79,6 @@ REDIS_URL=redis://localhost:6379
# Used by the 'src/scripts/checkOnChainProvidersAreInSync.ts' script
ALCHEMY_API_KEY=...
+# See the equivalent value configured in app sample
SCROLL_BADGE_PROVIDER_INFO='{"badge_provider":{"contractAddress":"0x...","level":1}}'
SCROLL_BADGE_ATTESTATION_SCHEMA_UID=0xd57de4f41c3d3cc855eadef68f98c0d4edd22d57161d96b7c06d2f4336cc3b49
diff --git a/iam/src/utils/scrollDevBadge.ts b/iam/src/utils/scrollDevBadge.ts
index 495db730ec..83d7f2c9d7 100644
--- a/iam/src/utils/scrollDevBadge.ts
+++ b/iam/src/utils/scrollDevBadge.ts
@@ -182,7 +182,7 @@ export const scrollDevBadgeHandler = (req: Request, res: Response): void => {
const requiredLevels = [...Array(maxCredentialLevel).keys()]
.map((n) => n + 1)
- .filter((n) => n > onchainLevel);
+ .filter((n) => n > onchainLevel && credentialLevels.includes(n));
// All credentials already claimed
if (requiredLevels.length === 0) return;
diff --git a/platforms/src/CustomGithub/Providers/condition.ts b/platforms/src/CustomGithub/Providers/condition.ts
index a7e5e8a1ee..e033594bc8 100644
--- a/platforms/src/CustomGithub/Providers/condition.ts
+++ b/platforms/src/CustomGithub/Providers/condition.ts
@@ -87,7 +87,7 @@ export const evaluateRepositoryContributor = async (
evaluator: ConditionEvaluator,
context: any
): Promise => {
- const threshold = condition["threshold"];
+ const threshold = 1; //condition["threshold"];
const repository = condition["repository"];
if (!(threshold !== undefined && threshold !== null) || !repository) {
@@ -139,7 +139,7 @@ export const evaluateRepositoryCommiter = async (
evaluator: ConditionEvaluator,
context: any
): Promise => {
- const threshold = condition["threshold"];
+ const threshold = 1; // condition["threshold"];
const repository = condition["repository"];
const cutOffDate = condition["cutoff_date"] ? new Date(condition["cutoff_date"]) : undefined;
diff --git a/platforms/src/CustomGithub/__tests__/github.test.ts b/platforms/src/CustomGithub/__tests__/github.test.ts
index 054788ca09..7505013e13 100644
--- a/platforms/src/CustomGithub/__tests__/github.test.ts
+++ b/platforms/src/CustomGithub/__tests__/github.test.ts
@@ -130,7 +130,7 @@ describe("CustomGithubProvider verification", function () {
});
expect(fetchAndCheckCommitCountToRepositoryMock).toHaveBeenCalledWith(
mockGithubContext,
- 3,
+ 1,
3,
"passportxyz/passport",
undefined
@@ -147,7 +147,7 @@ describe("CustomGithubProvider verification", function () {
condition: {
repository_commit_count: {
repository: "passportxyz/passport",
- threshold: 3,
+ threshold: 1,
cutoff_date: "2021-10-05T14:48:00.000Z",
},
},
@@ -189,7 +189,7 @@ describe("CustomGithubProvider verification", function () {
});
expect(fetchAndCheckCommitCountToRepositoryMock).toHaveBeenCalledWith(
mockGithubContext,
- 3,
+ 1,
3,
"passportxyz/passport",
new Date("2021-10-05T14:48:00.000Z")