Skip to content

Commit

Permalink
feat(platforms): add zkSync Era credential (passportxyz#1320)
Browse files Browse the repository at this point in the history
* feat: add zkSync Era credential

* fix(provider): updating zksync era credential to work

* feat: add zkSync Era tests

* chore: lint fix

---------

Co-authored-by: Michael Green <[email protected]>
  • Loading branch information
chibie and michaelgreen06 authored Jun 1, 2023
1 parent 04cc8e3 commit 4441d08
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 25 deletions.
7 changes: 6 additions & 1 deletion platforms/src/ZkSync/Providers-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PlatformSpec, PlatformGroupSpec, Provider } from "../types";
import { ZkSyncProvider } from "./Providers/zkSync";
import { ZkSyncEraProvider } from "./Providers/zkSyncEra";

export const PlatformDetails: PlatformSpec = {
icon: "./assets/zksyncStampIcon.svg",
Expand All @@ -15,6 +16,10 @@ export const ProviderConfig: PlatformGroupSpec[] = [
platformGroup: "zkSync 1.0",
providers: [{ title: "Transacted on zkSync 1.0", name: "ZkSync" }],
},
{
platformGroup: "zkSync Era",
providers: [{ title: "Transacted on zkSync Era", name: "ZkSyncEra" }],
},
];

export const providers: Provider[] = [new ZkSyncProvider()];
export const providers: Provider[] = [new ZkSyncProvider(), new ZkSyncEraProvider()];
14 changes: 6 additions & 8 deletions platforms/src/ZkSync/Providers/zkSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import axios from "axios";
import { getAddress } from "../../utils/signer";

// https://docs.zksync.io/api/v0.2/
export const zkSyncApiEnpoint = "https://api.zksync.io/api/v0.2/";
export const zkSyncApiEndpoint = "https://api.zksync.io/api/v0.2/";

// Pagination structure
type Pagination = {
Expand All @@ -33,7 +33,7 @@ type ZkSyncResponse = {
pagination: Pagination;
};

// Export a ZkSyncProvider Provider
// Export a Provider to verify ZkSync Transactions
export class ZkSyncProvider implements Provider {
// Give the provider a type so that we can select it with a payload
type = "ZkSync";
Expand All @@ -55,7 +55,7 @@ export class ZkSyncProvider implements Provider {
const address = (await getAddress(payload)).toLowerCase();

try {
const requestResponse = await axios.get(`${zkSyncApiEnpoint}accounts/${address}/transactions`, {
const requestResponse = await axios.get(`${zkSyncApiEndpoint}accounts/${address}/transactions`, {
params: {
from: "latest",
limit: 100,
Expand All @@ -71,11 +71,9 @@ export class ZkSyncProvider implements Provider {
// transaction initiated by this account
for (let i = 0; i < zkSyncResponse.result.list.length; i++) {
const t = zkSyncResponse.result.list[i];
if (t.status === "finalized") {
if (t.op.from === address) {
valid = true;
break;
}
if (t.status === "finalized" && t.op.from === address) {
valid = true;
break;
}
}

Expand Down
86 changes: 86 additions & 0 deletions platforms/src/ZkSync/Providers/zkSyncEra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// ----- Types
import type { Provider, ProviderOptions } from "../../types";
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";

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

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

// This is used in the Era Explorer
export const zkSyncEraApiEndpoint = "https://zksync2-mainnet-explorer.zksync.io/";

type ZKSyncEraTransaction = {
initiatorAddress: string;
status: string;
};

type ZkSyncEraResponse = {
list: ZKSyncEraTransaction[];
total: number;
};

// Export a Provider to verify ZkSync Era Transactions
export class ZkSyncEraProvider implements Provider {
// Give the provider a type so that we can select it with a payload
type = "ZkSyncEra";

// 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 at least 1 verified transaction
async verify(payload: RequestPayload): Promise<VerifiedPayload> {
// if a signer is provider we will use that address to verify against
let valid = false;
let error = undefined;

const address = (await getAddress(payload)).toLowerCase();

try {
const requestResponse = await axios.get(`${zkSyncEraApiEndpoint}transactions`, {
params: {
limit: 100,
direction: "older",
accountAddress: address,
},
});

if (requestResponse.status == 200) {
const zkSyncResponse = requestResponse.data as ZkSyncEraResponse;

// We consider the verification valid if this account has at least one verified
// transaction initiated by this account
for (let i = 0; i < zkSyncResponse.list.length; i++) {
const t = zkSyncResponse.list[i];
if (t.status === "verified" && t.initiatorAddress === address) {
valid = true;
break;
}
}

if (!valid) {
error = ["Unable to find a verified transaction from the given address"];
}
} else {
error = [`HTTP Error '${requestResponse.status}'. Details: '${requestResponse.statusText}'.`];
}
} catch (exc) {
error = ["Error getting transaction list for address"];
}
return Promise.resolve({
valid: valid,
record: valid
? {
address: address,
}
: undefined,
error,
});
}
}
32 changes: 16 additions & 16 deletions platforms/src/ZkSync/__tests__/zkSync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RequestPayload } from "@gitcoin/passport-types";

// ----- Libs
import axios from "axios";
import { zkSyncApiEnpoint, ZkSyncProvider } from "../Providers/zkSync";
import { zkSyncApiEndpoint, ZkSyncProvider } from "../Providers/zkSync";

jest.mock("axios");

Expand Down Expand Up @@ -75,7 +75,7 @@ const inValidResponseListAddressNotInFromField = [
},
];

const inValidResponseListNoFinalizedTranzaction = [
const inValidResponseListNoFinalizedTransaction = [
{
txHash: "0xsome_hash",
op: {
Expand Down Expand Up @@ -127,9 +127,9 @@ describe("Verification succeeds", function () {
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

// Check the request to get the NFTs
// Check the request to get the transactions
expect(axios.get).toHaveBeenCalledTimes(1);
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEnpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEndpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
params: {
from: "latest",
limit: 100,
Expand Down Expand Up @@ -163,9 +163,9 @@ describe("Verification fails", function () {
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

// Check the request to get the NFTs
// Check the request to get the transactions
expect(axios.get).toHaveBeenCalledTimes(1);
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEnpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEndpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
params: {
from: "latest",
limit: 100,
Expand All @@ -179,12 +179,12 @@ describe("Verification fails", function () {
});
});

it("when the response list does not contain any finalized tranzactions", async () => {
it("when the response list does not contain any finalized transactions", async () => {
(axios.get as jest.Mock).mockImplementation(() => {
return Promise.resolve({
status: 200,
data: {
result: { list: inValidResponseListNoFinalizedTranzaction },
result: { list: inValidResponseListNoFinalizedTransaction },
status: "success",
},
});
Expand All @@ -195,9 +195,9 @@ describe("Verification fails", function () {
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

// Check the request to get the NFTs
// Check the request to get the transactions
expect(axios.get).toHaveBeenCalledTimes(1);
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEnpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEndpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
params: {
from: "latest",
limit: 100,
Expand Down Expand Up @@ -228,9 +228,9 @@ describe("Verification fails", function () {
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

// Check the request to get the NFTs
// Check the request to get the transactions
expect(axios.get).toHaveBeenCalledTimes(1);
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEnpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEndpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
params: {
from: "latest",
limit: 100,
Expand Down Expand Up @@ -261,9 +261,9 @@ describe("Verification fails", function () {
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

// Check the request to get the NFTs
// Check the request to get the transactions
expect(axios.get).toHaveBeenCalledTimes(1);
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEnpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEndpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
params: {
from: "latest",
limit: 100,
Expand All @@ -287,9 +287,9 @@ describe("Verification fails", function () {
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

// Check the request to get the NFTs
// Check the request to get the transactions
expect(axios.get).toHaveBeenCalledTimes(1);
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEnpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
expect(mockedAxios.get).toBeCalledWith(`${zkSyncApiEndpoint}accounts/${MOCK_ADDRESS_LOWER}/transactions`, {
params: {
from: "latest",
limit: 100,
Expand Down
Loading

0 comments on commit 4441d08

Please sign in to comment.