Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Proof of Passport to Stamps #2489

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions app/context/ceramicContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const {
Civic,
TrustaLabs,
Outdid,
ProofOfPassport,
} = stampPlatforms;
import { PlatformProps } from "../components/GenericPlatform";

Expand Down Expand Up @@ -183,6 +184,11 @@ if (process.env.NEXT_PUBLIC_FF_OUTDID_STAMP === "on") {
});
}

platforms.set("ProofOfPassport", {
platform: new ProofOfPassport.ProofOfPassportPlatform(),
platFormGroupSpec: ProofOfPassport.ProviderConfig,
});

if (process.env.NEXT_PUBLIC_FF_GUILD_STAMP === "on") {
platforms.set("GuildXYZ", {
platform: new GuildXYZ.GuildXYZPlatform(),
Expand Down
997 changes: 997 additions & 0 deletions app/public/assets/ProofofPassportStampIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions iam/.env-example.env
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ IAM_JWK='{"kty":"OKP","crv":"Ed25519","x":"a7wbszn1DfZ3I7-_zDkUXCgypcGxL_cpCSTYE
IAM_PORT=8003

RPC_URL=https://eth-mainnet.alchemyapi.io/v2/<API_KEY>
OP_RPC_URL=https://opt-mainnet.alchemyapi.io/v2/<API_KEY>
ALCHEMY_API_KEY=<API_KEY>
MORALIS_API_KEY=<API_KEY>

Expand Down
24 changes: 24 additions & 0 deletions platforms/src/ProofofPassport/App-Bindings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//App-bindings.ts - EVM
import { AppContext, ProviderPayload } from "../types";
import { Platform } from "../utils/platform";

export class ProofOfPassportPlatform extends Platform {
platformId = "ProofOfPassport";
path = "ProofOfPassport";
clientId: string = null;
redirectUri: string = null;
isEVM = true;
banner = {
heading: "Proof of Passport",
cta: {
label: "Learn more",
url: "https://proofofpassport.com",
},
};

async getProviderPayload(appContext: AppContext): Promise<ProviderPayload> {
const result = await Promise.resolve({});
return result;
}
}

27 changes: 27 additions & 0 deletions platforms/src/ProofofPassport/Providers-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PlatformSpec, PlatformGroupSpec, Provider } from "../types";
import { ProofOfPassportProvider } from "./Providers";

export const PlatformDetails: PlatformSpec = {
icon: "./assets/ProofOfPassportStampIcon.svg",
platform: "ProofOfPassport",
name: "Proof of Passport",
description:
"Scan the NFC chip inside your passport to prove your humanity, powered by ZK for complete anonymity. Open source project.",
connectMessage: "Connect Account",
website: "https://proofofpassport.com/",
};

export const ProviderConfig: PlatformGroupSpec[] = [
{
platformGroup: "Name of the Stamp platform group",
providers: [
{
title: "Prove your humanity with Proof of Passport",
description: "Powered by ZK cryptography to ensure complete anonymity. Open source project.",
name: "ProofOfPassport",
},
],
},
];

export const providers: Provider[] = [new ProofOfPassportProvider()];
1 change: 1 addition & 0 deletions platforms/src/ProofofPassport/Providers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ProofOfPassportProvider } from "./proofOfPassport";
48 changes: 48 additions & 0 deletions platforms/src/ProofofPassport/Providers/proofOfPassport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Provider, ProviderOptions } from "../../types";
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";

import { getTokenBalance } from "./utils";

export const OP_RPC_URL = process.env.OP_RPC_URL;

export type ethErc721PossessionProviderOptions = {
threshold: number;
contractAddress: string;
decimalNumber: number;
};

export class ProofOfPassportProvider implements Provider {
type = "ProofOfPassport";

_options: ethErc721PossessionProviderOptions = {
threshold: 1,
contractAddress: "0x98aA4401ef9d3dFed09D8c98B5a62FA325CF23b3",
decimalNumber: 0,
};

constructor(options: ProviderOptions = {}) {
this._options = { ...this._options, ...options };
}

async verify(payload: RequestPayload): Promise<VerifiedPayload> {
const { address } = payload;
const errors = [];

const amount = await getTokenBalance(address, this._options.contractAddress, this._options.decimalNumber, payload);
const valid = amount >= this._options.threshold;

if (!valid) {
errors.push("Proof of Passport SBT not found for your address");
}

return {
valid,
record: valid
? {
address: address.toLocaleLowerCase(),
}
: {},
errors,
};
}
}
32 changes: 32 additions & 0 deletions platforms/src/ProofofPassport/Providers/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RequestPayload } from "@gitcoin/passport-types";
import { Contract } from "@ethersproject/contracts";
import { getOptimismRPCProvider } from "../../utils/signer";

const ERC721_ABI = [
{
inputs: [{ internalType: "address", name: "owner", type: "address" }],
name: "balanceOf",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
payable: false,
stateMutability: "view",
type: "function",
constant: true,
},
];

type TokenBalanceContract = {
balanceOf: (address: string) => Promise<string>;
};

export async function getTokenBalance(
address: string,
tokenContractAddress: string,
decimalNumber: number,
payload: RequestPayload
): Promise<number> {
const staticProvider = getOptimismRPCProvider(payload);
const readContract = new Contract(tokenContractAddress, ERC721_ABI, staticProvider);
const balanceOfFunc = readContract.balanceOf as (address: string) => Promise<string>;
const tokenBalance = await balanceOfFunc(address);
return parseFloat(tokenBalance);
}
54 changes: 54 additions & 0 deletions platforms/src/ProofofPassport/__tests__/proofOfPassport.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* eslint-disable */
import { ProofOfPassportProvider } from "../Providers/proofOfPassport";
import { getTokenBalance } from "../Providers/utils";
import { RequestPayload } from "@gitcoin/passport-types";

jest.mock("../Providers/utils", () => ({
getTokenBalance: jest.fn(),
}));

const MOCK_ADDRESS = "0x3336A5a627672A39967efa3Cd281e8e08E235ce2";
const MOCK_ADDRESS_LOWER = MOCK_ADDRESS.toLocaleLowerCase();

beforeEach(() => {
jest.clearAllMocks();
});

const mockTokenAddress = "0x5550ab114E3cf857b5bDd195eA9f753FAFd1cA91";

describe("ProofOfPassportProvider Tests", () => {
let proofOfPassportProvider: ProofOfPassportProvider;

beforeEach(() => {
proofOfPassportProvider = new ProofOfPassportProvider({
threshold: 1,
recordAttribute: "tokenCount",
contractAddress: mockTokenAddress,
decimalNumber: 0,
error: "Token balance fetch error",
});
});

it("should verify token balance is above threshold", async () => {
(getTokenBalance as jest.Mock).mockResolvedValueOnce(1);

const result = await proofOfPassportProvider.verify({
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

expect(result.valid).toBe(true);
expect(result.record).toEqual({
address: MOCK_ADDRESS_LOWER,
});
});

it("should reverse with a balance of 0", async () => {
(getTokenBalance as jest.Mock).mockResolvedValueOnce(0);

const result = await proofOfPassportProvider.verify({
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

expect(result.valid).toBe(false);
});
});
3 changes: 3 additions & 0 deletions platforms/src/ProofofPassport/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { ProofOfPassportPlatform } from "./App-Bindings";
export { ProofOfPassportProvider } from "./Providers";
export { PlatformDetails, providers, ProviderConfig } from "./Providers-config";
2 changes: 2 additions & 0 deletions platforms/src/platforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as Idena from "./Idena";
import * as Civic from "./Civic";
import * as TrustaLabs from "./TrustaLabs";
import * as Outdid from "./Outdid";
import * as ProofOfPassport from "./ProofOfPassport";
import { PlatformSpec, PlatformGroupSpec, Provider } from "./types";

type PlatformConfig = {
Expand Down Expand Up @@ -52,6 +53,7 @@ const platforms: Record<string, PlatformConfig> = {
Civic,
TrustaLabs,
Outdid,
ProofOfPassport,
};

if (process.env.NEXT_PUBLIC_FF_NEW_POAP_STAMPS === "on") {
Expand Down
7 changes: 7 additions & 0 deletions platforms/src/utils/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ import { utils } from "ethers";

// set the network rpc url based on env
const RPC_URL = process.env.RPC_URL;
const OP_RPC_URL = process.env.OP_RPC_URL;

export const getRPCProvider = (payload: RequestPayload): StaticJsonRpcProvider => {
const provider: StaticJsonRpcProvider = new StaticJsonRpcProvider(RPC_URL);

return provider;
};

export const getOptimismRPCProvider = (payload: RequestPayload): StaticJsonRpcProvider => {
const provider: StaticJsonRpcProvider = new StaticJsonRpcProvider(OP_RPC_URL);

return provider;
};

// get the address associated with the signer in the payload
export const getAddress = async ({ address, signer }: RequestPayload): Promise<string> => {
// if signer proof is provided, check validity and return associated address instead of controller
Expand Down
6 changes: 4 additions & 2 deletions types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ export type PLATFORM_ID =
| "GrantsStack"
| "ZkSync"
| "TrustaLabs"
| "Outdid";
| "Outdid"
| "ProofOfPassport";

export type PROVIDER_ID =
| "Signer"
Expand Down Expand Up @@ -432,7 +433,8 @@ export type PROVIDER_ID =
| "ETHDaysActive#50"
| "ETHGasSpent#0.25"
| "ETHnumTransactions#100"
| "Outdid";
| "Outdid"
| "ProofOfPassport";

export type StampBit = {
bit: number;
Expand Down