Skip to content

Commit

Permalink
chore(platforms): use holonym api instead of contract
Browse files Browse the repository at this point in the history
  • Loading branch information
calebtuttle committed Jan 24, 2024
1 parent 13f1974 commit 8d3ed6b
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 53 deletions.
51 changes: 25 additions & 26 deletions platforms/src/Holonym/Providers/holonymGovIdProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,20 @@
import { ProviderExternalVerificationError, type Provider, type ProviderOptions } from "../../types";
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";

// ----- Ethers library
import { Contract } from "ethers";
import { AlchemyProvider } from "@ethersproject/providers";
// ----- Libs
import axios from "axios";

// ----- Credential verification
import { getAddress } from "../../utils/signer";

export const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY;
// ----- Utils
import { handleProviderAxiosError } from "../../utils/handleProviderAxiosError";

const GOV_ID_SR_ADDRESS = "0xdD748977BAb5782625AF1466F4C5F02Eb92Fce31";
export const holonymApiEndpoint = "https://api.holonym.io/sybil-resistance/gov-id/optimism";

// ABI for Holonym Sybil resistance contract based on government ID
const GOV_ID_SR_ABI = [
{
inputs: [
{ internalType: "address", name: "", type: "address" },
{ internalType: "uint256", name: "", type: "uint256" },
],
name: "isUniqueForAction",
outputs: [{ internalType: "bool", name: "unique", type: "bool" }],
stateMutability: "view",
type: "function",
},
];
type SybilResistanceResponse = {
result: boolean;
};

const actionId = 123456789;

Expand All @@ -49,14 +39,9 @@ export class HolonymGovIdProvider implements Provider {
valid = false;

try {
// define a provider using the alchemy api key
const provider: AlchemyProvider = new AlchemyProvider("optimism", ALCHEMY_API_KEY);

const contract = new Contract(GOV_ID_SR_ADDRESS, GOV_ID_SR_ABI, provider);

// Query contract for user's uniqueness
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
valid = await contract.isUniqueForAction(address, actionId);
// Check if address is unique for default Holonym action ID
const response = await getIsUnique(address);
valid = response.result;

if (valid) {
record = {
Expand All @@ -83,3 +68,17 @@ export class HolonymGovIdProvider implements Provider {
}
}
}

const getIsUnique = async (address: string): Promise<SybilResistanceResponse> => {
try {
const requestResponse = await axios.get(`${holonymApiEndpoint}?user=${address}&action-id=${actionId}`);

if (requestResponse.status != 200) {
throw [`HTTP Error '${requestResponse.status}'. Details: '${requestResponse.statusText}'.`];
}

return requestResponse.data as SybilResistanceResponse;
} catch (error: unknown) {
handleProviderAxiosError(error, "holonym", [address]);
}
};
58 changes: 31 additions & 27 deletions platforms/src/Holonym/__tests__/holonymGovId.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,50 @@ import { RequestPayload } from "@gitcoin/passport-types";
import { ProviderExternalVerificationError } from "../../types";
import { HolonymGovIdProvider } from "../Providers/holonymGovIdProvider";

const mockIsUniqueForAction = jest.fn();
// ----- Libs
import axios from "axios";

jest.mock("ethers", () => {
return {
Contract: jest.fn().mockImplementation(() => {
return {
isUniqueForAction: mockIsUniqueForAction,
};
}),
};
});
jest.mock("axios");

const mockedAxios = axios as jest.Mocked<typeof axios>;

const MOCK_ADDRESS = "0xb4b6f1c68be31841b52f4015a31d1f38b99cdb71";
const actionId = 123456789;

describe("Attempt verification", function () {
beforeEach(() => {
jest.clearAllMocks();
});

it("should return true for an address that has proven uniqueness to Holonym government ID Sybil resistance smart contract", async () => {
mockIsUniqueForAction.mockResolvedValueOnce(true);
it("should return true when valid response is received from the Holonym API endpoint", async () => {
mockedAxios.get.mockResolvedValueOnce({
status: 200,
data: {
result: true,
},
});

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

expect(mockIsUniqueForAction).toHaveBeenCalledWith(MOCK_ADDRESS, actionId);
expect(verifiedPayload).toEqual({
valid: true,
errors: [],
record: {
address: MOCK_ADDRESS,
},
});
expect(verifiedPayload.valid).toBe(true);
});

it("should return false for an address that has not proven uniqueness to Holonym government ID Sybil resistance smart contract", async () => {
mockIsUniqueForAction.mockResolvedValueOnce(false);
const UNREGISTERED_ADDRESS = "0xunregistered";
it("should return false when invalid response is received from the Holonym API endpoint", async () => {
mockedAxios.get.mockResolvedValueOnce({
status: 200,
data: {
result: false,
},
});

const holonym = new HolonymGovIdProvider();
const verifiedPayload = await holonym.verify({
address: UNREGISTERED_ADDRESS,
address: MOCK_ADDRESS,
} as RequestPayload);

expect(mockIsUniqueForAction).toHaveBeenCalledWith(UNREGISTERED_ADDRESS, actionId);
expect(verifiedPayload.valid).toBe(false);
expect(verifiedPayload).toEqual({
valid: false,
errors: ["We were unable to verify that your address was unique for action -- isUniqueForAction: false."],
Expand All @@ -58,7 +55,14 @@ describe("Attempt verification", function () {
});

it("should return error response when isUniqueForAction call errors", async () => {
mockIsUniqueForAction.mockRejectedValueOnce("some error");
mockedAxios.get.mockRejectedValueOnce({
status: 500,
response: {
data: {
error: "Internal Server Error",
},
},
});
const UNREGISTERED_ADDRESS = "0xunregistered";

const holonym = new HolonymGovIdProvider();
Expand Down

0 comments on commit 8d3ed6b

Please sign in to comment.