diff --git a/platforms/src/ProofofPassport/App-Bindings.ts b/platforms/src/ProofofPassport/App-Bindings.ts new file mode 100644 index 0000000000..e2457526d8 --- /dev/null +++ b/platforms/src/ProofofPassport/App-Bindings.ts @@ -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 { + const result = await Promise.resolve({}); + return result; + } +} \ No newline at end of file diff --git a/platforms/src/ProofofPassport/Providers-config.ts b/platforms/src/ProofofPassport/Providers-config.ts new file mode 100644 index 0000000000..d3c44a2d39 --- /dev/null +++ b/platforms/src/ProofofPassport/Providers-config.ts @@ -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()] + diff --git a/platforms/src/ProofofPassport/Providers/index.ts b/platforms/src/ProofofPassport/Providers/index.ts new file mode 100644 index 0000000000..b78b9a7b96 --- /dev/null +++ b/platforms/src/ProofofPassport/Providers/index.ts @@ -0,0 +1 @@ +export { ProofOfPassportProvider } from "./proofOfPassport"; diff --git a/platforms/src/ProofofPassport/Providers/proofOfPassport.ts b/platforms/src/ProofofPassport/Providers/proofOfPassport.ts new file mode 100644 index 0000000000..9b88a4adf4 --- /dev/null +++ b/platforms/src/ProofofPassport/Providers/proofOfPassport.ts @@ -0,0 +1,54 @@ +import type { Provider, ProviderOptions } from "../../types"; +import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types"; + +import { getTokenBalance } from "./utils"; + +export const RPC_URL = process.env.RPC_URL; + +export type ethErc721PossessionProviderOptions = { + threshold: number; + contractAddress: string; + decimalNumber: number; + error: string; +}; + +export class ProofOfPassportProvider implements Provider { + type = ""; + + _options: ethErc721PossessionProviderOptions = { + threshold: 1, + contractAddress: "0x98aA4401ef9d3dFed09D8c98B5a62FA325CF23b3", + decimalNumber: 0, + error: "Coin Possession Provider Error", + }; + + constructor(options: ProviderOptions = {}) { + this._options = { ...this._options, ...options }; + } + + async verify(payload: RequestPayload): Promise { + const { address } = payload; + let valid = false; + let amount = 0; + try { + amount = await getTokenBalance(address, this._options.contractAddress, this._options.decimalNumber, payload); + + } catch (e) { + return { + valid: false, + errors: [this._options.error], + }; + } finally { + console.log("amount:", amount); + valid = amount >= this._options.threshold; + } + return { + valid, + record: valid + ? { + address: address.toLocaleLowerCase() + } + : {}, + }; + } +} \ No newline at end of file diff --git a/platforms/src/ProofofPassport/Providers/utils.ts b/platforms/src/ProofofPassport/Providers/utils.ts new file mode 100644 index 0000000000..1fa3005372 --- /dev/null +++ b/platforms/src/ProofofPassport/Providers/utils.ts @@ -0,0 +1,29 @@ + +import { RequestPayload } from "@gitcoin/passport-types"; +import { Contract } from "@ethersproject/contracts"; +import { getRPCProvider } 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 + } +] +export async function getTokenBalance( + address: string, + tokenContractAddress: string, + decimalNumber: number, + payload: RequestPayload +): Promise { + const staticProvider = getRPCProvider(payload); + console.log("tokenContractAddress:", tokenContractAddress); + console.log("address", address); + console.log("decimalNumber:", decimalNumber); + const readContract = new Contract(tokenContractAddress, ERC721_ABI, staticProvider); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const tokenBalance: string = await readContract?.balanceOf(address); + console.log("tokenBalance call:", tokenBalance); + return parseFloat(tokenBalance); +} \ No newline at end of file diff --git a/platforms/src/ProofofPassport/__tests__/proofOfPassport.test.ts b/platforms/src/ProofofPassport/__tests__/proofOfPassport.test.ts new file mode 100644 index 0000000000..8fab073c71 --- /dev/null +++ b/platforms/src/ProofofPassport/__tests__/proofOfPassport.test.ts @@ -0,0 +1,57 @@ +/* eslint-disable */ +import { JsonRpcSigner, JsonRpcProvider } from "@ethersproject/providers"; +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); + }); + +}); diff --git a/platforms/src/ProofofPassport/assets/ProofofPassportStampIcon.svg b/platforms/src/ProofofPassport/assets/ProofofPassportStampIcon.svg new file mode 100644 index 0000000000..df7f077b29 --- /dev/null +++ b/platforms/src/ProofofPassport/assets/ProofofPassportStampIcon.svg @@ -0,0 +1,997 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platforms/src/ProofofPassport/index.ts b/platforms/src/ProofofPassport/index.ts new file mode 100644 index 0000000000..bbe5d7ddd3 --- /dev/null +++ b/platforms/src/ProofofPassport/index.ts @@ -0,0 +1,3 @@ +export { ProofOfPassportPlatform } from "./App-Bindings"; +export { ProofOfPassportProvider } from "./Providers"; +export { PlatformDetails, providers } from "./Providers-config"; diff --git a/types/src/index.d.ts b/types/src/index.d.ts index 03e973cc85..cedde10ebf 100644 --- a/types/src/index.d.ts +++ b/types/src/index.d.ts @@ -358,7 +358,8 @@ export type PLATFORM_ID = | "GrantsStack" | "ZkSync" | "TrustaLabs" - | "Outdid"; + | "Outdid" + | "ProofofPassport"; export type PROVIDER_ID = | "Signer" @@ -432,7 +433,8 @@ export type PROVIDER_ID = | "ETHDaysActive#50" | "ETHGasSpent#0.25" | "ETHnumTransactions#100" - | "Outdid"; + | "Outdid" + | "ProofofPassport"; export type StampBit = { bit: number;