Skip to content

Commit

Permalink
1251 update smart contract integration (passportxyz#1279)
Browse files Browse the repository at this point in the history
* feat(iam, app): request fee from user and pass to smart contract

* fix(app): 2 dollar fee

* fix(app): testing the 2 dollar fee

* fix(iam): cleaning up logs, wrapping up tests

* fix(iam): fix linter complaints

* fix(iam): fix failing tests, use default values for env vars

---------

Co-authored-by: nutrina <[email protected]>
  • Loading branch information
Tim Schultz and nutrina authored May 18, 2023
1 parent 88ff4dd commit 6437c97
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 64 deletions.
10 changes: 7 additions & 3 deletions app/components/SyncToChainButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import GitcoinAttester from "../contracts/GitcoinAttester.json";
import { CeramicContext } from "../context/ceramicContext";
import { UserContext } from "../context/userContext";

import { VerifiableCredential } from "@gitcoin/passport-types";
import { VerifiableCredential, EasPayload } from "@gitcoin/passport-types";

const SyncToChainButton = () => {
const { passport } = useContext(CeramicContext);
Expand All @@ -26,7 +26,10 @@ const SyncToChainButton = () => {
setSyncingToChain(true);
const credentials = passport.stamps.map(({ credential }: { credential: VerifiableCredential }) => credential);

const { data } = await axios.post(`${process.env.NEXT_PUBLIC_PASSPORT_IAM_URL}v0.0.0/eas`, credentials);
const { data }: { data: EasPayload } = await axios.post(
`${process.env.NEXT_PUBLIC_PASSPORT_IAM_URL}v0.0.0/eas`,
credentials
);

if (data.error) console.log("error syncing credentials to chain: ", data.error, "credentials: ", credentials);
if (data.invalidCredentials.length > 0)
Expand All @@ -47,7 +50,8 @@ const SyncToChainButton = () => {
data.passport,
v,
r,
s
s,
{ value: data.passport.fee }
);
toast({
title: "Submitted",
Expand Down
13 changes: 0 additions & 13 deletions iam/__mocks__/ethers.js

This file was deleted.

84 changes: 70 additions & 14 deletions iam/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ import {
VerifiedPayload,
} from "@gitcoin/passport-types";

import { utils } from "ethers";
import * as easFeesMock from "../src/utils/easFees";
import * as identityMock from "@gitcoin/passport-identity/dist/commonjs/src/credentials";

jest.mock("ethers", () => {
const originalModule = jest.requireActual("ethers");
const ethers = originalModule.ethers;
const utils = originalModule.utils;

return {
utils: {
...utils,
getAddress: jest.fn().mockImplementation(() => {
return "0x0";
}),
Expand All @@ -29,15 +36,7 @@ jest.mock("ethers", () => {
return { v: 0, r: "r", s: "s" };
}),
},
ethers: {
Wallet: jest.fn().mockImplementation(() => {
return {
_signTypedData: jest.fn().mockImplementation(() => {
return new Promise((r) => r("0xSignedData"));
}),
};
}),
},
ethers,
};
});

Expand All @@ -46,12 +45,12 @@ jest.mock("@ethereum-attestation-service/eas-sdk", () => {
SchemaEncoder: jest.fn().mockImplementation(() => {
return {
encodeData: jest.fn().mockImplementation(() => {
return "0xEncodedData";
return "0x1234";
}),
};
}),
ZERO_BYTES32: "0x0000000000000000000000000000000000000000000000000000000000000000",
NO_EXPIRATION: -1,
NO_EXPIRATION: 0,
};
});

Expand Down Expand Up @@ -708,7 +707,6 @@ describe("POST /check", function () {
.expect(200)
.expect("Content-Type", /json/);

console.log(response.body);
expect(response.body.length).toEqual(0);
});
});
Expand All @@ -718,6 +716,11 @@ describe("POST /eas", () => {
jest.spyOn(identityMock, "verifyCredential").mockResolvedValue(true);
});

afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();
});

it("handles valid requests including some invalid credentials", async () => {
const failedCredential = {
"@context": "https://www.w3.org/2018/credentials/v1",
Expand Down Expand Up @@ -755,11 +758,11 @@ describe("POST /eas", () => {
provider: "test",
stampHash: "test",
expirationDate: "9999-12-31T23:59:59Z",
encodedData: "0xEncodedData",
encodedData: "0x1234",
},
],
recipient: "0x5678000000000000000000000000000000000000",
expirationTime: -1,
expirationTime: 0,
revocable: true,
refUID: "0x0000000000000000000000000000000000000000000000000000000000000000",
value: 0,
Expand Down Expand Up @@ -841,4 +844,57 @@ describe("POST /eas", () => {

expect(response.body.error).toEqual("Invalid recipient");
});

it("returns the fee information in the response as wei units", async () => {
const getEASFeeAmountSpy = jest
.spyOn(easFeesMock, "getEASFeeAmount")
.mockReturnValue(Promise.resolve(utils.parseEther("0.025")));
const expectedFeeUsd = 2;

const credentials = [
{
"@context": "https://www.w3.org/2018/credentials/v1",
type: ["VerifiableCredential", "Stamp"],
issuer: config.issuer,
issuanceDate: new Date().toISOString(),
credentialSubject: {
id: "did:pkh:eip155:1:0x5678000000000000000000000000000000000000",
provider: "test",
hash: "test",
},
expirationDate: "9999-12-31T23:59:59Z",
},
];

const expectedPayload = {
passport: {
stamps: [
{
provider: "test",
stampHash: "test",
expirationDate: "9999-12-31T23:59:59Z",
encodedData: "0x1234",
},
],
recipient: "0x5678000000000000000000000000000000000000",
expirationTime: 0,
revocable: true,
refUID: "0x0000000000000000000000000000000000000000000000000000000000000000",
value: 0,
fee: "25000000000000000",
},
signature: expect.any(Object),
};

const response = await request(app)
.post("/api/v0.0.0/eas")
.send(credentials)
.set("Accept", "application/json")
.expect(200)
.expect("Content-Type", /json/);

expect(response.body).toMatchObject(expectedPayload);
expect(getEASFeeAmountSpy).toHaveBeenCalledTimes(1);
expect(getEASFeeAmountSpy).toHaveBeenCalledWith(expectedFeeUsd);
});
});
48 changes: 16 additions & 32 deletions iam/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ import {
CheckRequestBody,
CheckResponseBody,
VerifiableCredential,
EasStamp,
EasPayload,
} from "@gitcoin/passport-types";

import { getChallenge } from "./utils/challenge";
import { getEASFeeAmount } from "./utils/easFees";

// ---- Generate & Verify methods
import * as DIDKit from "@spruceid/didkit-wasm-node";
Expand All @@ -55,14 +58,16 @@ export const config: {
issuer,
};

const attestationSignerWallet = new ethers.Wallet(process.env.ATTESTATION_SIGNER_PRIVATE_KEY);
const attestationSignerWallet = new ethers.Wallet(
process.env.ATTESTATION_SIGNER_PRIVATE_KEY || "0x04d16281ff3bf268b29cdd684183f72542757d24ae9fdfb863e7c755e599163a"
);
const attestationSchemaEncoder = new SchemaEncoder("string provider, string hash");

const ATTESTER_DOMAIN = {
name: "Attester",
version: "1",
chainId: process.env.GITCOIN_ATTESTER_CHAIN_ID,
verifyingContract: process.env.GITCOIN_ATTESTER_CONTRACT_ADDRESS,
chainId: process.env.GITCOIN_ATTESTER_CHAIN_ID || "11155111",
verifyingContract: process.env.GITCOIN_ATTESTER_CONTRACT_ADDRESS || "0xD8088f772006CAFD81082e8e2e467fA18564e879",
};

const ATTESTER_TYPES = {
Expand All @@ -79,6 +84,7 @@ const ATTESTER_TYPES = {
{ name: "revocable", type: "bool" },
{ name: "refUID", type: "bytes32" },
{ name: "value", type: "uint256" },
{ name: "fee", type: "uint256" },
],
};

Expand Down Expand Up @@ -350,32 +356,6 @@ app.post("/api/v0.0.0/verify", (req: Request, res: Response): void => {
});
});

export type EasStamp = {
provider: string;
stampHash: string;
expirationDate: string;
encodedData: string;
};

type EasPassport = {
stamps: EasStamp[];
recipient: string;
expirationTime: number;
revocable: boolean;
refUID: string;
value: number;
};

type EasPayload = {
passport: EasPassport;
signature: {
v: number;
r: string;
s: string;
};
invalidCredentials: VerifiableCredential[];
};

// Expose entry point for getting eas payload for moving stamps on-chain
// This function will receive an array of stamps, validate them and return an array of eas payloads
app.post("/api/v0.0.0/eas", (req: Request, res: Response): void => {
Expand All @@ -395,7 +375,7 @@ app.post("/api/v0.0.0/eas", (req: Request, res: Response): void => {
};
})
)
.then((credentialVerifications) => {
.then(async (credentialVerifications) => {
const invalidCredentials = credentialVerifications
.filter(({ verified }) => !verified)
.map(({ credential }) => credential);
Expand All @@ -417,13 +397,16 @@ app.post("/api/v0.0.0/eas", (req: Request, res: Response): void => {

if (!stamps.length) return void errorRes(res, "No verifiable stamps provided", 400);

const easPassport: EasPassport = {
const fee = await getEASFeeAmount(2);

const easPassport = {
stamps,
recipient,
expirationTime: NO_EXPIRATION,
revocable: true,
refUID: ZERO_BYTES32,
value: 0,
fee: fee.toString(),
};

attestationSignerWallet
Expand All @@ -440,11 +423,12 @@ app.post("/api/v0.0.0/eas", (req: Request, res: Response): void => {
return void res.json(payload);
})
.catch((error) => {
// TODO dont return real error
console.error(error);
return void errorRes(res, String(error), 500);
});
})
.catch((error) => {
console.error(error);
// TODO dont return real error
return void errorRes(res, String(error), 500);
});
Expand Down
21 changes: 21 additions & 0 deletions iam/src/utils/easFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import axios from "axios";
import { utils } from "ethers";
import { BigNumber } from "@ethersproject/bignumber";

export async function getEASFeeAmount(usdAmount: number): Promise<BigNumber> {
const ethUSD: {
data: {
ethereum: {
usd: number;
};
};
} = await axios.get("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=USD", {
headers: {
// TODO add API key
Accept: "application/json",
},
});

const ethAmount = usdAmount / ethUSD.data.ethereum.usd;
return utils.parseEther(ethAmount.toFixed(18));
}
4 changes: 2 additions & 2 deletions types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
"clean": "rimraf dist *.tsbuildinfo",
"lint": "tsc --noEmit && eslint --ext .ts,.js,.tsx .",
"prettier": "prettier --write .",
"precommit": "lint-staged"
"precommit": "lint-staged",
"ethers": "^5.0.32"
},
"devDependencies": {
"ethers": "^5.0.32",
"tslint": "^6.1.3",
"typescript": "~4.6.3"
}
Expand Down
29 changes: 29 additions & 0 deletions types/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { JsonRpcSigner } from "@ethersproject/providers";
// BrightId Shared Types
export { BrightIdProcedureResponse, BrightIdVerificationResponse, BrightIdSponsorshipResponse } from "./brightid";
import { BigNumber } from "@ethersproject/bignumber";

// Typing for required parts of DIDKit
export type DIDKitLib = {
Expand Down Expand Up @@ -166,6 +167,34 @@ export type PassportLoadResponse = {
errorDetails?: PassportLoadErrorDetails;
};

export type EasStamp = {
provider: string;
stampHash: string;
expirationDate: string;
encodedData: string;
};

export type EasPassport = {
stamps: EasStamp[];
recipient: string;
expirationTime: any;
revocable: boolean;
refUID: string;
value: any;
fee: any;
};

export type EasPayload = {
passport: EasPassport;
signature: {
v: number;
r: string;
s: string;
};
invalidCredentials: VerifiableCredential[];
error?: string;
};

// Passport DID
export type DID = string;

Expand Down

0 comments on commit 6437c97

Please sign in to comment.