Skip to content

Commit

Permalink
feat(app, platforms): base trusta labs stamp
Browse files Browse the repository at this point in the history
  • Loading branch information
aminah-io authored Aug 15, 2023
2 parents 0bb096c + 1df288d commit af0898b
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 2 deletions.
1 change: 1 addition & 0 deletions app/.env-example.env
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ NEXT_PUBLIC_FF_ONE_CLICK_VERIFICATION=on
NEXT_PUBLIC_FF_LIVE_ALLO_SCORE=on
NEXT_PUBLIC_FF_NEW_TWITTER_STAMPS=on
NEXT_PUBLIC_FF_CYBERCONNECT_STAMPS=on
NEXT_PUBLIC_FF_TRUSTALABS_STAMPS=on

NEXT_PUBLIC_CERAMIC_CACHE_ENDPOINT=http://localhost:8002/

Expand Down
2 changes: 2 additions & 0 deletions app/components/PlatformCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export const PlatformCard = ({

if (process.env.NEXT_PUBLIC_FF_CYBERCONNECT_STAMPS !== "on" && platform.platform === "CyberConnect") return <></>;

if (process.env.NEXT_PUBLIC_FF_TRUSTALABS_STAMPS !== "on" && platform.platform === "TrustaLabs") return <></>;

// returns a single Platform card
return (
<div className={className} key={`${platform.name}${i}`}>
Expand Down
8 changes: 8 additions & 0 deletions app/context/ceramicContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const {
Civic,
CyberConnect,
GrantsStack,
TrustaLabs,
} = stampPlatforms;
import { PlatformProps } from "../components/GenericPlatform";

Expand Down Expand Up @@ -235,6 +236,13 @@ platforms.set("GrantsStack", {
platFormGroupSpec: GrantsStack.ProviderConfig,
});

if (process.env.NEXT_PUBLIC_FF_TRUSTALABS_STAMPS === "on") {
platforms.set("TrustaLabs", {
platform: new TrustaLabs.TrustaLabsPlatform(),
platFormGroupSpec: TrustaLabs.ProviderConfig,
});
}

export enum IsLoadingPassportState {
Idle,
Loading,
Expand Down
1 change: 1 addition & 0 deletions app/public/assets/trustaLabsStampIcon.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 @@ -24,6 +24,7 @@ GTC_STAKING_GRAPH_API_KEY=MY_API_KEY
COINBASE_CLIENT_ID=MY_COINBASE_CLIENT_ID
COINBASE_CLIENT_SECRET=MY_COINBASE_CLIENT_SECRET
COINBASE_CALLBACK=http://localhost:3000/
TRUSTA_LABS_ACCESS_TOKEN=trusta_labs_access_token

CURRENT_ENV=development
EXIT_ON_UNHANDLED_ERROR=true
Expand Down
12 changes: 12 additions & 0 deletions platforms/src/TrustaLabs/App-Bindings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AppContext, ProviderPayload } from "../types";
import { Platform } from "../utils/platform";

export class TrustaLabsPlatform extends Platform {
platformId = "TrustaLabs";
path = "TrustaLabs";
isEVM = true;

async getProviderPayload(_appContext: AppContext): Promise<ProviderPayload> {
return await Promise.resolve({});
}
}
16 changes: 16 additions & 0 deletions platforms/src/TrustaLabs/Providers-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { PlatformSpec, PlatformGroupSpec, Provider } from "../types";
import { TrustaLabsProvider } from "./Providers/TrustaLabs";

export const PlatformDetails: PlatformSpec = {
icon: "./assets/trustalabsStampIcon.svg",
platform: "TrustaLabs",
name: "Trusta Labs",
description: "Connect your existing TrustaLabs Account to verify",
connectMessage: "Connect Account",
};

export const ProviderConfig: PlatformGroupSpec[] = [
{ platformGroup: "Trusta Labs", providers: [{ title: "Sybil Score", name: "TrustaLabs" }] },
];

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

// ----- Libs
import axios from "axios";

// ----- Credential verification
import { ProviderExternalVerificationError } from "../../types";

interface SubScore {
bulkOperationRisk: number;
starlikeAssetsNetworkRisk: number;
chainlikeAssetsNetworkRisk: number;
similarBehaviorSequenceRisk: number;
blacklistRisk: number;
}

interface TrustaLabsData {
address: string;
sybilRiskScore: number;
sybilRiskLevel: string;
subScore: SubScore;
}

interface TrustaLabsResponse {
data: TrustaLabsData;
success?: boolean;
code?: number;
message?: string;
}

interface AxiosResponse {
data: TrustaLabsResponse;
}

const TRUSTA_LABS_API_ENDPOINT = "https://www.trustalabs.ai/service/openapi/queryRiskSummaryScore";

// Based on https://axios-http.com/docs/handling_errors
const handleAxiosError = (error: any, label: string, secretsToHide?: string[]) => {
if (axios.isAxiosError(error)) {
let message = `Error making ${label} request, `;
if (error.response) {
// Received a non 2xx response
const { data, status, headers } = error.response;
message += `received error response with code ${status}: ${JSON.stringify(data)}, headers: ${JSON.stringify(
headers
)}`;
} else if (error.request) {
// No response received
message += "no response received";
} else {
// Something happened in setting up the request that triggered an Error
message += error.message;
}
secretsToHide?.forEach((secret) => {
message = message.replace(secret, "[SECRET]");
});
throw new ProviderExternalVerificationError(message);
}
throw error;
};

const createUpdateDBScore = async (address: string, score: number) => {
const accessToken = process.env.CGRANTS_API_TOKEN;
try {
await axios.post(
`${process.env.SCORER_ENDPOINT}/trusta_labs/trusta-labs-score`,
{ address, score },
{
headers: {
Authorization: accessToken,
},
}
);
} catch (error) {
handleAxiosError(error, "report score", [accessToken]);
}
};

const makeSybilScoreRequest = async (address: string) => {
const accessToken = process.env.TRUSTA_LABS_ACCESS_TOKEN;
try {
const result: AxiosResponse = await axios.post(
TRUSTA_LABS_API_ENDPOINT,
{
address,
chainId: "1",
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
return result.data.data.sybilRiskScore;
} catch (error) {
handleAxiosError(error, "sybil score", [accessToken]);
}
};

const verifyTrustaLabsRiskScore = async (address: string): Promise<{ valid: boolean; errors: string[] }> => {
const sybilRiskScore = await makeSybilScoreRequest(address);
await createUpdateDBScore(address, sybilRiskScore);

const lowerBound = -1;
const upperBound = 60;

if (sybilRiskScore >= lowerBound && sybilRiskScore <= upperBound) {
return {
valid: true,
errors: [],
};
} else {
return {
valid: false,
errors: [`Sybil score ${sybilRiskScore} is outside of the allowed range (${lowerBound} to ${upperBound})`],
};
}
};

export class TrustaLabsProvider implements Provider {
type = "TrustaLabs";

// 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 };
}

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

// if a signer is provider we will use that address to verify against
const { valid, errors } = await verifyTrustaLabsRiskScore(address);

return {
valid,
errors,
record: {
address,
},
};
}
}
59 changes: 59 additions & 0 deletions platforms/src/TrustaLabs/Providers/__tests__/TrustaLabs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* eslint-disable */
// ---- Test subject
import { RequestPayload } from "@gitcoin/passport-types";
import { TrustaLabsProvider } from "../../Providers/TrustaLabs";

// ----- Libs
import axios from "axios";

jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;

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

const makeResponse = (score: number) => ({
data: {
data: {
address: MOCK_ADDRESS_LOWER,
sybilRiskScore: score,
},
success: true,
code: 0,
message: "",
},
});

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

describe("TrustaLabs", () => {
it.each([
[20, true],
[30.000045668, true],
[60, true],
[80, false],
[-1, true],
[-2, false],
])("should return %s for score %s", async (score: number, expected: boolean) => {
mockedAxios.post.mockResolvedValue(makeResponse(score));

const trustaLabs = new TrustaLabsProvider();
const verifiedPayload = await trustaLabs.verify({
address: MOCK_ADDRESS,
} as RequestPayload);

expect(axios.post).toHaveBeenCalledTimes(2);

const { valid, record, errors } = verifiedPayload;
if (expected) {
expect(valid).toBe(true);
expect(record).toEqual({ address: MOCK_ADDRESS });
expect(errors).toEqual([]);
} else {
expect(valid).toBe(false);
expect(errors).toEqual([`Sybil score ${score} is outside of the allowed range (-1 to 60)`]);
}
});
});
1 change: 1 addition & 0 deletions platforms/src/TrustaLabs/Providers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TrustaLabsProvider } from "./TrustaLabs";
3 changes: 3 additions & 0 deletions platforms/src/TrustaLabs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { TrustaLabsPlatform } from "./App-Bindings";
export { ProviderConfig, PlatformDetails, providers } from "./Providers-config";
export { TrustaLabsProvider } from "./Providers/TrustaLabs";
2 changes: 2 additions & 0 deletions platforms/src/platforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as Idena from "./Idena";
import * as Civic from "./Civic";
import * as CyberConnect from "./CyberProfile";
import * as GrantsStack from "./GrantsStack";
import * as TrustaLabs from "./TrustaLabs";
import { PlatformSpec, PlatformGroupSpec, Provider } from "./types";

type PlatformConfig = {
Expand Down Expand Up @@ -62,6 +63,7 @@ const platforms: Record<string, PlatformConfig> = {
Civic,
CyberConnect,
GrantsStack,
TrustaLabs,
};

if (process.env.NEXT_PUBLIC_FF_NEW_POAP_STAMPS === "on") {
Expand Down
1 change: 1 addition & 0 deletions platforms/src/utils/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const UPDATED_PROVIDERS = [
"twitterAccountAgeGte#180",
"twitterAccountAgeGte#365",
"twitterAccountAgeGte#730",
"TrustaLabs",
"Simple",
];

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 @@ -228,7 +228,8 @@ export type PLATFORM_ID =
| "Idena"
| "Civic"
| "CyberConnect"
| "GrantsStack";
| "GrantsStack"
| "TrustaLabs";

export type PROVIDER_ID =
| "Signer"
Expand Down Expand Up @@ -329,7 +330,8 @@ export type PROVIDER_ID =
| "GrantsStack7Projects"
| "GrantsStack2Programs"
| "GrantsStack4Programs"
| "GrantsStack6Programs";
| "GrantsStack6Programs"
| "TrustaLabs";

export type StampBit = {
bit: number;
Expand Down

0 comments on commit af0898b

Please sign in to comment.