Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Get the EVM address for a given Farcaster User #114

Merged
merged 14 commits into from
Feb 13, 2024
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@changesets/cli": "^2.26.2",
"@testing-library/jest-dom": "^6.4.0",
"@testing-library/react": "^14.2.0",
"@types/jest": "^29.5.11",
"@types/jest": "^29.5.12",
Sneh1999 marked this conversation as resolved.
Show resolved Hide resolved
"@types/react": "^18",
"@types/react-dom": "^18",
"jest": "^29.7.0",
Expand Down
90 changes: 90 additions & 0 deletions src/core/getFarcasterUserAddresses.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { neynarGetCustodyAddressesForFid } from '../utils/neynar/frame/neynarGetCustodyAddressForFid';
Sneh1999 marked this conversation as resolved.
Show resolved Hide resolved
import { neynarGetVerifiedAddressesForFid } from '../utils/neynar/frame/neynarGetVerifiedAddressesForFid';
import { FarcasterAddressType } from './types';
import { getFarcasterUserAddresses } from './getFarcasterUserAddresses';

jest.mock('../utils/neynar/frame/neynarGetCustodyAddressForFid', () => ({
neynarGetCustodyAddressesForFid: jest.fn(),
}));

jest.mock('../utils/neynar/frame/neynarGetVerifiedAddressesForFid', () => ({
neynarGetVerifiedAddressesForFid: jest.fn(),
}));

describe('getFarcasterUserAddresses function', () => {
afterEach(() => {
jest.clearAllMocks();
});

test('should return verified address when FarcasterAddressType is FarcasterAddressType.VerifiedAddresses', async () => {
const fid = 1234;
const expectedAddress = 'verified address';
const getFarcasterUserAddressesRequest = {
fid,
farcasterAddressType: FarcasterAddressType.VerifiedAddresses,
};

(neynarGetVerifiedAddressesForFid as jest.Mock).mockResolvedValue([expectedAddress]);

const result = await getFarcasterUserAddresses(getFarcasterUserAddressesRequest);

expect(neynarGetVerifiedAddressesForFid).toHaveBeenCalledWith(fid, undefined);
expect(result).toEqual([expectedAddress]);
});

test('should return custody address when FarcasterAddressType is FarcasterAddressType.CustodyAddress', async () => {
const fid = 12345;
const expectedAddress = 'custody address';
const getFarcasterUserAddressesRequest = {
fid,
farcasterAddressType: FarcasterAddressType.CustodyAddress,
};

(neynarGetCustodyAddressesForFid as jest.Mock).mockResolvedValue(expectedAddress);

const result = await getFarcasterUserAddresses(getFarcasterUserAddressesRequest);

expect(neynarGetCustodyAddressesForFid).toHaveBeenCalledWith(fid, undefined);
expect(result).toEqual(expectedAddress);
});

test('should throw an error if an exception occurs', async () => {
const fid = 1234;
const error = new Error('Something went wrong');
const getFarcasterUserAddressesRequest = {
fid,
farcasterAddressType: FarcasterAddressType.VerifiedAddresses,
};

(neynarGetVerifiedAddressesForFid as jest.Mock).mockRejectedValue(error);

await expect(getFarcasterUserAddresses(getFarcasterUserAddressesRequest)).rejects.toThrow(
error,
);
});

test('should return verified address when FarcasterAddressType is FarcasterAddressType.VerifiedAddresses and neynarApiKey is not undefined', async () => {
const fid = 1234;
const neynarApiKey = 'neynar';
const expectedAddress = 'verified address';
const anotherExpectedAddress = 'verified address-2';

const getFarcasterUserAddressesRequest = {
fid,
farcasterAddressType: FarcasterAddressType.VerifiedAddresses,
};
const options = {
neynarApiKey,
};

(neynarGetVerifiedAddressesForFid as jest.Mock).mockResolvedValue([
expectedAddress,
anotherExpectedAddress,
]);

const result = await getFarcasterUserAddresses(getFarcasterUserAddressesRequest, options);

expect(neynarGetVerifiedAddressesForFid).toHaveBeenCalledWith(fid, neynarApiKey);
expect(result).toEqual([expectedAddress, anotherExpectedAddress]);
});
});
35 changes: 35 additions & 0 deletions src/core/getFarcasterUserAddresses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { neynarGetCustodyAddressesForFid } from '../utils/neynar/frame/neynarGetCustodyAddressForFid';
import { neynarGetVerifiedAddressesForFid } from '../utils/neynar/frame/neynarGetVerifiedAddressesForFid';
import { FarcasterAddressType, GetFarcasterUserAddressesRequest } from './types';

type GetUserAddressOptions =
| {
neynarApiKey?: string;
}
| undefined;

/**
* Get the user address for a given fid
* @param fid The frame id
* @param FarcasterAddressType represents whether the client wants a verified address or a custody address
* @returns the validation addresses or the custory address. If there is an error, it throws an error
*/

async function getFarcasterUserAddresses(
getFarcasterUserAddressRequest: GetFarcasterUserAddressesRequest,
options?: GetUserAddressOptions,
): Promise<string | string[]> {
try {
const { fid, farcasterAddressType } = getFarcasterUserAddressRequest;
if (farcasterAddressType === FarcasterAddressType.VerifiedAddresses) {
const addresses = await neynarGetVerifiedAddressesForFid(fid, options?.neynarApiKey);
return addresses;
}
const address = await neynarGetCustodyAddressesForFid(fid, options?.neynarApiKey);
return address;
} catch (e) {
throw e;
}
}

export { FarcasterAddressType, getFarcasterUserAddresses };
20 changes: 20 additions & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,23 @@ export type FrameMetadataType = {
* Note: exported as public Type
*/
export type FrameMetadataResponse = Record<string, string>;

/**
* Frame address type to determine if the client is request a verified address or a custody address
* Note: exported as public Type
*/

export enum FarcasterAddressType {
VerifiedAddresses,
CustodyAddress,
}

/**
* Get User Address Request
*
* Note: exported as public Type
*/
export interface GetFarcasterUserAddressesRequest {
fid: number;
farcasterAddressType: FarcasterAddressType;
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 🌲☀️🌲
export { version } from './version';
export { getFrameHtmlResponse } from './core/getFrameHtmlResponse';
export { getFarcasterUserAddresses } from './core/getFarcasterUserAddresses';
export { getFrameMetadata } from './core/getFrameMetadata';
export { getFrameMessage } from './core/getFrameMessage';
export { FrameMetadata } from './components/FrameMetadata';
Expand All @@ -15,4 +16,6 @@ export type {
FrameMetadataType,
FrameRequest,
FrameValidationData,
FarcasterAddressType,
GetFarcasterUserAddressesRequest,
} from './core/types';
30 changes: 30 additions & 0 deletions src/utils/neynar/frame/neynarGetCustodyAddressForFid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { version } from '../../../version';
import { FetchError } from '../exceptions/FetchError';
import { NEYNAR_DEFAULT_API_KEY } from './neynarFrameFunctions';

export async function neynarGetCustodyAddressesForFid(
fid: number,
apiKey: string = NEYNAR_DEFAULT_API_KEY,
): Promise<string> {
const options = {
method: 'GET',
url: `https://api.neynar.com/v1/farcaster/custody-address?fid=${fid}`,
headers: {
accept: 'application/json',
api_key: apiKey,
'content-type': 'application/json',
onchainkit_version: version,
},
};
const resp = await fetch(options.url, options);
if (resp.status !== 200) {
throw new FetchError(`non-200 status returned from neynar : ${resp.status}`);
}
const responseBody = await resp.json();

if (!responseBody || !responseBody.result || !responseBody.result.custodyAddress) {
throw new Error('No verified addresses found for FID ' + fid);
}

return responseBody.result.custodyAddress;
}
35 changes: 35 additions & 0 deletions src/utils/neynar/frame/neynarGetVerifiedAddressesForFid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { version } from '../../../version';
import { FetchError } from '../exceptions/FetchError';
import { NEYNAR_DEFAULT_API_KEY } from './neynarFrameFunctions';

export async function neynarGetVerifiedAddressesForFid(
fid: number,
apiKey: string = NEYNAR_DEFAULT_API_KEY,
): Promise<string[]> {
const options = {
method: 'GET',
url: `https://api.neynar.com/v1/farcaster/verifications?fid=${fid}`,
headers: {
accept: 'application/json',
api_key: apiKey,
'content-type': 'application/json',
onchainkit_version: version,
},
};
const resp = await fetch(options.url, options);
if (resp.status !== 200) {
throw new FetchError(`non-200 status returned from neynar : ${resp.status}`);
}
const responseBody = await resp.json();

if (
!responseBody ||
!responseBody.result ||
!responseBody.result.verifications ||
responseBody.result.verifications.length === 0
) {
throw new Error('No verified addresses found for FID ' + fid);
}

return responseBody.result.verifications;
}
17 changes: 17 additions & 0 deletions src/utils/neynar/neynar.integ.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { neynarBulkUserLookup } from './user/neynarUserFunctions';
import { neynarFrameValidation } from './frame/neynarFrameFunctions';
import { neynarGetCustodyAddressesForFid } from './frame/neynarGetCustodyAddressForFid';
import { neynarGetVerifiedAddressesForFid } from './frame/neynarGetVerifiedAddressesForFid';

describe('neynar integration tests', () => {
it('bulk data lookup should find all users', async () => {
Expand Down Expand Up @@ -27,4 +29,19 @@ describe('neynar integration tests', () => {
);
expect(response?.interactor.verified_accounts.length).toBeGreaterThan(0);
});

it('get custody address for FID returns correct address', async () => {
const fid = 3;
const response = await neynarGetCustodyAddressesForFid(fid);
expect(response).toEqual('0x6b0bda3f2ffed5efc83fa8c024acff1dd45793f1');
});

it('get verified addresses for FID returns correct addresses', async () => {
const fid = 3;
const response = await neynarGetVerifiedAddressesForFid(fid);
expect(response).toEqual([
'0x8fc5d6afe572fefc4ec153587b63ce543f6fa2ea',
'0xd7029bdea1c17493893aafe29aad69ef892b8ff2',
]);
});
});
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ __metadata:
"@changesets/cli": "npm:^2.26.2"
"@testing-library/jest-dom": "npm:^6.4.0"
"@testing-library/react": "npm:^14.2.0"
"@types/jest": "npm:^29.5.11"
"@types/jest": "npm:^29.5.12"
"@types/react": "npm:^18"
"@types/react-dom": "npm:^18"
jest: "npm:^29.7.0"
Expand Down Expand Up @@ -1834,13 +1834,13 @@ __metadata:
languageName: node
linkType: hard

"@types/jest@npm:^29.5.11":
version: 29.5.11
resolution: "@types/jest@npm:29.5.11"
"@types/jest@npm:^29.5.12":
version: 29.5.12
resolution: "@types/jest@npm:29.5.12"
dependencies:
expect: "npm:^29.0.0"
pretty-format: "npm:^29.0.0"
checksum: 524a3394845214581278bf4d75055927261fbeac7e1a89cd621bd0636da37d265fe0a85eac58b5778758faad1cbd7c7c361dfc190c78ebde03a91cce33463261
checksum: 25fc8e4c611fa6c4421e631432e9f0a6865a8cb07c9815ec9ac90d630271cad773b2ee5fe08066f7b95bebd18bb967f8ce05d018ee9ab0430f9dfd1d84665b6f
languageName: node
linkType: hard

Expand Down
Loading