Skip to content

Commit

Permalink
feat(iam): adding stamp for holding at least 1 NFT
Browse files Browse the repository at this point in the history
  • Loading branch information
nutrina committed Aug 24, 2022
1 parent b66bf60 commit 7f91db4
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 0 deletions.
80 changes: 80 additions & 0 deletions iam/__tests__/nft.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// ---- Test subject
import { alchemyGetNFTsUrl, NFTProvider } from "../src/providers/nft";

import { RequestPayload } from "@gitcoin/passport-types";

// ----- 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();

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

describe("Attempt verification", function () {
it.only.each([
[200, 0, false],
[200, 1, true],
[200, 200, true],
[300, 0, false],
[400, 1, false],
[500, 200, false],
])(
" - when status is %p and totalCount is %p valid es expected to be %p",
async (httpStatus, totalCount: number, expectedValid: boolean) => {
(axios.get as jest.Mock).mockImplementation((url) => {
return Promise.resolve({
status: httpStatus,
data: {
totalCount: totalCount,
ownedNfts: [],
},
});
});

const nftProvider = new NFTProvider();
const nftPayload = await nftProvider.verify({
address: MOCK_ADDRESS,
} as unknown as RequestPayload);

// Check the request to get the NFTs
expect(axios.get).toHaveBeenCalledTimes(1);
expect(mockedAxios.get).toBeCalledWith(alchemyGetNFTsUrl, {
params: {
withMetadata: "false",
owner: MOCK_ADDRESS_LOWER,
},
});

if (expectedValid) {
expect(nftPayload).toEqual({
valid: true,
record: {
numTotalNFTs: totalCount.toString(),
},
});
} else {
expect(nftPayload).toEqual({
valid: false,
});
}
}
);

it("should return invalid payload when unable to get NFTs (exception thrown)", async () => {
mockedAxios.get.mockImplementation(async () => {
throw "some kind of error";
});

const nftProvider = new NFTProvider();
const nftPayload = await nftProvider.verify({} as unknown as RequestPayload);

expect(axios.get).toHaveBeenCalledTimes(1);
expect(nftPayload).toMatchObject({ valid: false });
});
});
75 changes: 75 additions & 0 deletions iam/src/providers/nft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// ----- 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";

// Alchemy Api key
export const apiKey = process.env.ALCHEMY_API_KEY;
export const alchemyGetNFTsUrl = `https://eth-mainnet.g.alchemy.com/nft/v2/${apiKey}/getNFTs`;

type NFTsResponse = {
ownedNfts: [];
totalCount: number;
};

// Export a NFT Provider
export class NFTProvider implements Provider {
// Give the provider a type so that we can select it with a payload
type = "NFT";

// 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 owns at least one POAP older than 15 days
async verify(payload: RequestPayload): Promise<VerifiedPayload> {
console.log("geri - verify", payload);
// if a signer is provider we will use that address to verify against
const address = (await getAddress(payload)).toLowerCase();

console.log("geri - address");
let valid = false;
let nftsResponse: NFTsResponse = {
ownedNfts: [],
totalCount: 0,
};

try {
console.log("geri - making request");
const requestResponse = await axios.get(alchemyGetNFTsUrl, {
params: {
withMetadata: "false",
owner: address,
},
});

if (requestResponse.status == 200) {
nftsResponse = requestResponse.data as NFTsResponse;

console.log("geri - nftsResponse", nftsResponse);
valid = nftsResponse.totalCount > 0;
}
} catch (error) {
// Nothing to do here, valid will remain false
}

console.log("geri - valid", valid);
return Promise.resolve({
valid: valid,
record: valid
? {
numTotalNFTs: nftsResponse.totalCount.toString(),
}
: undefined,
});
}
}

0 comments on commit 7f91db4

Please sign in to comment.