forked from passportxyz/passport
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(platforms): Cyberconnect stamp (passportxyz#1557)
* feat: adds cyberconnect profile stamp * feat: remove cyberprofile free tier stamp, adds cyberconnect org member stamp * feat(app, platforms): include cyberconnect in front end * chore(platforms): use context for duplicate requests * chore(platforms): mock requests instead of making external calls * fix(platforms): return use handle for record * feat(platforms): return unique org membership from nonevm * chore(app): feature flag cyberconnect * fix(platforms): remove eslint disable * fix(types): remove duplicate type --------- Co-authored-by: HaoPeiwen <[email protected]>
- Loading branch information
schultztimothy
and
HaoPeiwen
authored
Aug 8, 2023
1 parent
901c868
commit 147d484
Showing
12 changed files
with
556 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { AppContext, ProviderPayload } from "../types"; | ||
import { Platform } from "../utils/platform"; | ||
|
||
export class CyberConnectPlatform extends Platform { | ||
platformId = "CyberConnect"; | ||
path = "CyberConnect"; | ||
clientId: string = null; | ||
redirectUri: string = null; | ||
isEVM = true; | ||
|
||
async getProviderPayload(appContext: AppContext): Promise<ProviderPayload> { | ||
const result = await Promise.resolve({}); | ||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { PlatformSpec, PlatformGroupSpec, Provider } from "../types"; | ||
import { CyberProfilePremiumProvider, CyberProfilePaidProvider } from "./Providers/cyberconnect"; | ||
import { CyberProfileOrgMemberProvider } from "./Providers/cyberconnect_nonevm"; | ||
|
||
export const PlatformDetails: PlatformSpec = { | ||
icon: "./assets/cyberconnectLogoIcon.svg", | ||
platform: "CyberConnect", | ||
name: "CyberConnect", | ||
description: "Connect your wallet to verify your CyberProfile Handle.", | ||
connectMessage: "Verify Account", | ||
isEVM: true, | ||
}; | ||
|
||
export const ProviderConfig: PlatformGroupSpec[] = [ | ||
{ | ||
platformGroup: "CyberProfile Handle", | ||
providers: [ | ||
{ title: "Premium CyberProfile Handle ( length is between 1 and 6 characters )", name: "CyberProfilePremium" }, | ||
{ title: "Paid CyberProfile Handle ( length is between 7 and 12 characters )", name: "CyberProfilePaid" }, | ||
{ title: "Organization Membership", name: "CyberProfileOrgMember" }, | ||
], | ||
}, | ||
]; | ||
|
||
export const providers: Provider[] = [ | ||
new CyberProfilePremiumProvider(), | ||
new CyberProfilePaidProvider(), | ||
new CyberProfileOrgMemberProvider(), | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// ----- Types | ||
import type { Provider, ProviderOptions } from "../../types"; | ||
import type { ProviderContext, RequestPayload, VerifiedPayload } from "@gitcoin/passport-types"; | ||
|
||
// ----- Ethers library | ||
import { Contract, BigNumber } from "ethers"; | ||
import { StaticJsonRpcProvider } from "@ethersproject/providers"; | ||
|
||
// CyberProfile Proxy Contract Address | ||
const CYBERPROFILE_PROXY_CONTRACT_ADDRESS = "0x2723522702093601e6360CAe665518C4f63e9dA6"; | ||
|
||
// CyberProfile Proxy ABI functions needed to get the length of primary profile handle | ||
const CYBERPROFILE_PROXY_ABI = [ | ||
{ | ||
inputs: [{ internalType: "address", name: "user", type: "address" }], | ||
name: "getPrimaryProfile", | ||
outputs: [{ internalType: "uint256", name: "", type: "uint256" }], | ||
stateMutability: "view", | ||
type: "function", | ||
}, | ||
{ | ||
inputs: [{ internalType: "uint256", name: "profileId", type: "uint256" }], | ||
name: "getHandleByProfileId", | ||
outputs: [{ internalType: "string", name: "", type: "string" }], | ||
stateMutability: "view", | ||
type: "function", | ||
}, | ||
]; | ||
|
||
export type GithubContext = ProviderContext & { | ||
cyberConnect?: { | ||
handle?: string; | ||
}; | ||
}; | ||
|
||
export type CyberConnectHandleResponse = { | ||
handle?: string; | ||
errors?: string[]; | ||
}; | ||
|
||
interface CyberProfileContract extends Contract { | ||
getPrimaryProfile?(address: string): Promise<BigNumber>; | ||
getHandleByProfileId?(id: number): Promise<string>; | ||
// add other methods as needed | ||
} | ||
|
||
// return 0 if no primary handle is found, otherwise return the length of the primary handle | ||
export async function getPrimaryHandle( | ||
userAddress: string, | ||
context: GithubContext | ||
): Promise<CyberConnectHandleResponse> { | ||
if (!context.cyberConnect?.handle) { | ||
const provider: StaticJsonRpcProvider = new StaticJsonRpcProvider( | ||
process.env.BSC_RPC_URL || "https://bsc-dataseed.binance.org/" | ||
); | ||
|
||
const contract: CyberProfileContract = new Contract( | ||
CYBERPROFILE_PROXY_CONTRACT_ADDRESS, | ||
CYBERPROFILE_PROXY_ABI, | ||
provider | ||
); | ||
if (!context.cyberConnect) context.cyberConnect = {}; | ||
// get primary profile id | ||
const profileId: BigNumber = await contract.getPrimaryProfile(userAddress); | ||
// if no primary profile id is found (profileId == 0), return 0 | ||
if (profileId.isZero()) { | ||
context.cyberConnect.handle = ""; | ||
return context.cyberConnect; | ||
} | ||
// get primary profile handle | ||
const handle: string = await contract.getHandleByProfileId(profileId.toNumber()); | ||
|
||
context.cyberConnect.handle = handle; | ||
// return the length of the primary handle | ||
return context.cyberConnect; | ||
} | ||
return context.cyberConnect; | ||
} | ||
|
||
// Export a CyberProfilePremiumProvider | ||
export class CyberProfilePremiumProvider implements Provider { | ||
// Give the provider a type so that we can select it with a payload | ||
type = "CyberProfilePremium"; | ||
|
||
// Options can be set here and/or via the constructor | ||
_options = {}; | ||
|
||
// construct the provider instance with supplied options | ||
constructor(options: ProviderOptions = {}) { | ||
this._options = { ...this._options, ...options }; | ||
} | ||
|
||
// Verify that address defined in the payload has a handle length <= 6 and > 0 | ||
async verify(payload: RequestPayload, context: ProviderContext): Promise<VerifiedPayload> { | ||
// if a signer is provider we will use that address to verify against | ||
const address = payload.address.toString().toLowerCase(); | ||
let valid = false; | ||
let userHandle: string; | ||
try { | ||
const { handle } = await getPrimaryHandle(address, context); | ||
userHandle = handle; | ||
} catch (e) { | ||
return { | ||
valid: false, | ||
error: ["CyberProfile provider get user primary handle error"], | ||
}; | ||
} | ||
const lengthOfPrimaryHandle = userHandle.length; | ||
valid = lengthOfPrimaryHandle <= 6 && lengthOfPrimaryHandle > 0; | ||
return Promise.resolve({ | ||
valid: valid, | ||
record: valid | ||
? { | ||
userHandle, | ||
} | ||
: {}, | ||
}); | ||
} | ||
} | ||
|
||
// Export a CyberProfilePaidProvider | ||
export class CyberProfilePaidProvider implements Provider { | ||
// Give the provider a type so that we can select it with a payload | ||
type = "CyberProfilePaid"; | ||
|
||
// Options can be set here and/or via the constructor | ||
_options = {}; | ||
|
||
// construct the provider instance with supplied options | ||
constructor(options: ProviderOptions = {}) { | ||
this._options = { ...this._options, ...options }; | ||
} | ||
|
||
// Verify that address defined in the payload has a handle length <= 12 and > 6 | ||
async verify(payload: RequestPayload, context: ProviderContext): Promise<VerifiedPayload> { | ||
// if a signer is provider we will use that address to verify against | ||
const address = payload.address.toString().toLowerCase(); | ||
let valid = false; | ||
let userHandle: string; | ||
try { | ||
const { handle } = await getPrimaryHandle(address, context); | ||
userHandle = handle; | ||
} catch (e) { | ||
return { | ||
valid: false, | ||
error: ["CyberProfile provider get user primary handle error"], | ||
}; | ||
} | ||
const lengthOfPrimaryHandle = userHandle.length; | ||
valid = lengthOfPrimaryHandle <= 12 && lengthOfPrimaryHandle > 6; | ||
return Promise.resolve({ | ||
valid: valid, | ||
record: valid | ||
? { | ||
userHandle, | ||
} | ||
: {}, | ||
}); | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
platforms/src/CyberProfile/Providers/cyberconnect_nonevm.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// ----- Types | ||
import type { Provider, ProviderOptions } from "../../types"; | ||
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types"; | ||
|
||
// ----- Libs | ||
import axios from "axios"; | ||
|
||
export const cyberconnectGraphQL = "https://api.cyberconnect.dev/"; | ||
|
||
// Defining interfaces for the data structure returned by the gql query | ||
interface CheckOrgMemberResponse { | ||
data: { | ||
data?: { | ||
checkVerifiedOrganizationMember?: { | ||
isVerifiedOrganizationMember: boolean; | ||
uniqueIdentifier: string; | ||
}; | ||
}; | ||
errors?: { | ||
message: string; | ||
}[]; | ||
}; | ||
} | ||
|
||
export const checkForOrgMember = async ( | ||
url: string, | ||
address: string | ||
): Promise<{ isMember: boolean; identifier: string }> => { | ||
let isMember = false; | ||
let identifier = ""; | ||
let result: CheckOrgMemberResponse; | ||
|
||
// Query the CyberConnect graphQL | ||
try { | ||
result = await axios.post(url, { | ||
query: ` | ||
query CheckOrgMember { | ||
checkVerifiedOrganizationMember ( | ||
address: "${address}" | ||
) | ||
{ | ||
isVerifiedOrganizationMember | ||
uniqueIdentifier | ||
} | ||
}`, | ||
}); | ||
if (result.data.errors) { | ||
throw result.data.errors[0].message; | ||
} | ||
} catch (e: unknown) { | ||
throw `The following error is being thrown: ${JSON.stringify(e)}`; | ||
} | ||
|
||
isMember = result.data.data.checkVerifiedOrganizationMember.isVerifiedOrganizationMember; | ||
identifier = result.data.data.checkVerifiedOrganizationMember.uniqueIdentifier; | ||
return { | ||
isMember, | ||
identifier, | ||
}; | ||
}; | ||
|
||
// Export a CyberProfileOrgMemberProvider | ||
export class CyberProfileOrgMemberProvider implements Provider { | ||
// Give the provider a type so that we can select it with a payload | ||
type = "CyberProfileOrgMember"; | ||
|
||
// Options can be set here and/or via the constructor | ||
_options = {}; | ||
|
||
// construct the provider instance with supplied options | ||
constructor(options: ProviderOptions = {}) { | ||
this._options = { ...this._options, ...options }; | ||
} | ||
|
||
// Verify that address defined in the payload has a handle length > 12 | ||
async verify(payload: RequestPayload): Promise<VerifiedPayload> { | ||
// if a signer is provider we will use that address to verify against | ||
const address = payload.address.toString().toLowerCase(); | ||
let valid = false; | ||
try { | ||
const { isMember, identifier } = await checkForOrgMember(cyberconnectGraphQL, address); | ||
valid = isMember ? true : false; | ||
return Promise.resolve({ | ||
valid: valid, | ||
record: valid | ||
? { | ||
orgMembership: identifier, | ||
} | ||
: {}, | ||
}); | ||
} catch (e) { | ||
return { | ||
valid: false, | ||
error: ["CyberProfile provider check organization membership error"], | ||
}; | ||
} | ||
} | ||
} |
Oops, something went wrong.