Skip to content

Commit

Permalink
feat: expose getName and getAvatar identity logic. #265 (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zizzamia committed Apr 11, 2024
1 parent 5cc61c5 commit b795268
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 90 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-jokes-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@coinbase/onchainkit': patch
---

- **feat**: exposed the `getName` and `getAvatar` utilities to assist in retrieving name and avatar identity information. These utilities come in handy when working with Next.js or any Node.js backend. By @zizzamia #265 #283
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

### Patch Changes

- 695b4a0: - **feat**: upgraded `@xmtp/frames-validator` package to [0.6.0](https://github.com/xmtp/xmtp-node-js-tools/pull/191). By @zizzamia #278 #277
- **feat**: upgraded `@xmtp/frames-validator` package to [0.6.0](https://github.com/xmtp/xmtp-node-js-tools/pull/191). By @zizzamia #278 #277 695b4a0

## 0.11.1

### Patch Changes

- 2301e64: - **feat**: include peer dependency for graphql@15 and graphql@16. By @benson-budiman-cb #270
- **feat**: include peer dependency for graphql@15 and graphql@16. By @benson-budiman-cb #270 2301e64

## 0.11.0

Expand Down
23 changes: 23 additions & 0 deletions site/docs/pages/identity/get-avatar.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# `getAvatar`

The `getAvatar` utility is designed to retrieve an avatar image
URL from an onchain identity provider for a given name.

Consider the utility instead of the hook when you
use it with Next.js or any Node.js backend.

Supported providers:

- ENS

## Usage

```tsx
import { getAvatar } from '@coinbase/onchainkit/identity';

const avatar = await getAvatar({ ensName: 'vitalik.eth' });
```

## Returns

[`Promise<GetAvatarReturnType>`](/identity/types#getavatarreturntype)
23 changes: 23 additions & 0 deletions site/docs/pages/identity/get-name.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# `getName`

The `getName` utility is designed to retrieve a name from an onchain identity
provider for a specific address.

Consider the utility instead of the hook when you
use it with Next.js or any Node.js backend.

Supported providers:

- ENS

## Usage

```tsx
import { getName } from '@coinbase/onchainkit/identity';

const name = await getName({ address: '0x1234' });
```

## Returns

[`Promise<GetNameReturnType>`](/identity/types#getnamereturntype)
12 changes: 12 additions & 0 deletions site/docs/pages/identity/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ type GetEASAttestationsOptions = {
limit?: number;
};
```

## `GetAvatarReturnType`

```ts
export type GetAvatarReturnType = string | null;
```

## `GetNameReturnType`

```ts
export type GetNameReturnType = string | null;
```
2 changes: 1 addition & 1 deletion site/docs/pages/identity/use-avatar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useAvatar } from '@coinbase/onchainkit/identity';
const { data: avatar, isLoading } = useAvatar({ ensName: 'vitalik.eth' });
```

## Props
## Returns

```ts
type UseAvatarOptions = {
Expand Down
2 changes: 1 addition & 1 deletion site/docs/pages/identity/use-name.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useName } from '@coinbase/onchainkit/identity';
const { data: name, isLoading } = useName({ address: '0x1234' });
```

## Props
## Returns

```ts
type UseNameOptions = {
Expand Down
8 changes: 8 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,18 @@ export const sidebar = [
{
text: 'Utilities',
items: [
{
text: 'getAvatar',
link: '/identity/get-avatar',
},
{
text: 'getEASAttestations',
link: '/identity/get-eas-attestations',
},
{
text: 'getName',
link: '/identity/get-name',
},
],
},
{
Expand Down
36 changes: 36 additions & 0 deletions src/identity/core/getAvatar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @jest-environment jsdom
*/

import { getAvatar } from './getAvatar';
import { publicClient } from '../../network/client';

jest.mock('../../network/client');

describe('getAvatar', () => {
const mockGetEnsAvatar = publicClient.getEnsAvatar as jest.Mock;

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

it('should return correct avatar URL from client getAvatar', async () => {
const ensName = 'test.ens';
const expectedAvatarUrl = 'avatarUrl';

mockGetEnsAvatar.mockResolvedValue(expectedAvatarUrl);

const avatarUrl = await getAvatar(ensName);

expect(avatarUrl).toBe(expectedAvatarUrl);
expect(mockGetEnsAvatar).toHaveBeenCalledWith({ name: ensName });
});

it('should return null when client getAvatar throws an error', async () => {
const ensName = 'test.ens';

mockGetEnsAvatar.mockRejectedValue(new Error('This is an error'));

await expect(getAvatar(ensName)).rejects.toThrow('This is an error');
});
});
9 changes: 9 additions & 0 deletions src/identity/core/getAvatar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { normalize } from 'viem/ens';
import { publicClient } from '../../network/client';
import { GetAvatarReturnType } from '../types';

export const getAvatar = async (ensName: string): Promise<GetAvatarReturnType> => {
return await publicClient.getEnsAvatar({
name: normalize(ensName),
});
};
40 changes: 40 additions & 0 deletions src/identity/core/getName.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @jest-environment jsdom
*/

import { getName } from './getName';
import { publicClient } from '../../network/client';

jest.mock('../../network/client');

describe('getName', () => {
const mockGetEnsName = publicClient.getEnsName as jest.Mock;

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

it('should return correct value from client getName', async () => {
const walletAddress = '0x1234';
const expectedEnsName = 'avatarUrl';
mockGetEnsName.mockResolvedValue(expectedEnsName);
const name = await getName(walletAddress);
expect(name).toBe(expectedEnsName);
expect(mockGetEnsName).toHaveBeenCalledWith({ address: walletAddress });
});

it('should return null name when client ', async () => {
const walletAddress = '0x1234';
const expectedEnsName = 'avatarUrl';
mockGetEnsName.mockResolvedValue(expectedEnsName);
const name = await getName(walletAddress);
expect(name).toBe(expectedEnsName);
expect(mockGetEnsName).toHaveBeenCalledWith({ address: walletAddress });
});

it('should return null client getName throws an error', async () => {
const walletAddress = '0x1234';
mockGetEnsName.mockRejectedValue(new Error('This is an error'));
await expect(getName(walletAddress)).rejects.toThrow('This is an error');
});
});
16 changes: 16 additions & 0 deletions src/identity/core/getName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Address } from 'viem';
import { publicClient } from '../../network/client';
import { GetNameReturnType } from '../types';

/**
* An asynchronous function to fetch the Ethereum Name Service (ENS) name for a given Ethereum address.
* It returns the ENS name if it exists, or null if it doesn't or in case of an error.
*
* @param address - The Ethereum address for which the ENS name is being fetched.
* @returns A promise that resolves to the ENS name (as a string) or null.
*/
export const getName = async (address: Address): Promise<GetNameReturnType> => {
return await publicClient.getEnsName({
address,
});
};
24 changes: 1 addition & 23 deletions src/identity/hooks/useAvatar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/

import { renderHook, waitFor } from '@testing-library/react';
import { useAvatar } from './useAvatar';
import { publicClient } from '../../network/client';
import { useAvatar, ensAvatarAction } from './useAvatar';
import { getNewReactQueryTestProvider } from '../../test-utils/hooks/get-new-react-query-test-provider';

jest.mock('../../network/client');
Expand Down Expand Up @@ -51,26 +51,4 @@ describe('useAvatar', () => {
expect(result.current.isLoading).toBe(true);
});
});

describe('ensAvatarAction', () => {
it('should return correct avatar URL from client getEnsAvatar', async () => {
const ensName = 'test.ens';
const expectedAvatarUrl = 'avatarUrl';

mockGetEnsAvatar.mockResolvedValue(expectedAvatarUrl);

const avatarUrl = await ensAvatarAction(ensName);

expect(avatarUrl).toBe(expectedAvatarUrl);
expect(mockGetEnsAvatar).toHaveBeenCalledWith({ name: ensName });
});

it('should return null when client getEnsAvatar throws an error', async () => {
const ensName = 'test.ens';

mockGetEnsAvatar.mockRejectedValue(new Error('This is an error'));

await expect(ensAvatarAction(ensName)).rejects.toThrow('This is an error');
});
});
});
14 changes: 4 additions & 10 deletions src/identity/hooks/useAvatar.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { publicClient } from '../../network/client';
import { type GetEnsAvatarReturnType, normalize } from 'viem/ens';
import { useQuery } from '@tanstack/react-query';

export const ensAvatarAction = async (ensName: string): Promise<GetEnsAvatarReturnType> => {
return await publicClient.getEnsAvatar({
name: normalize(ensName),
});
};
import { GetAvatarReturnType } from '../types';
import { getAvatar } from '../core/getAvatar';

type UseNameOptions = {
ensName: string;
Expand All @@ -23,10 +17,10 @@ type UseNameQueryOptions = {
export const useAvatar = ({ ensName }: UseNameOptions, queryOptions?: UseNameQueryOptions) => {
const { enabled = true, cacheTime } = queryOptions ?? {};
const ensActionKey = `ens-avatar-${ensName}`;
return useQuery<GetEnsAvatarReturnType>({
return useQuery<GetAvatarReturnType>({
queryKey: ['useAvatar', ensActionKey],
queryFn: async () => {
return await ensAvatarAction(ensName);
return await getAvatar(ensName);
},
gcTime: cacheTime,
enabled,
Expand Down
36 changes: 1 addition & 35 deletions src/identity/hooks/useName.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/

import { renderHook, waitFor } from '@testing-library/react';
import { useName } from './useName';
import { publicClient } from '../../network/client';
import { useName, ensNameAction } from './useName';
import { getNewReactQueryTestProvider } from '../../test-utils/hooks/get-new-react-query-test-provider';

jest.mock('../../network/client');
Expand Down Expand Up @@ -51,38 +51,4 @@ describe('useName', () => {
expect(result.current.isLoading).toBe(true);
});
});

describe('ensNameAction', () => {
it('should return correct value from client getEnsName', async () => {
const walletAddress = '0x1234';
const expectedEnsName = 'avatarUrl';

mockGetEnsName.mockResolvedValue(expectedEnsName);

const name = await ensNameAction(walletAddress);

expect(name).toBe(expectedEnsName);
expect(mockGetEnsName).toHaveBeenCalledWith({ address: walletAddress });
});

it('should return null name when client ', async () => {
const walletAddress = '0x1234';
const expectedEnsName = 'avatarUrl';

mockGetEnsName.mockResolvedValue(expectedEnsName);

const name = await ensNameAction(walletAddress);

expect(name).toBe(expectedEnsName);
expect(mockGetEnsName).toHaveBeenCalledWith({ address: walletAddress });
});

it('should return null client getEnsName throws an error', async () => {
const walletAddress = '0x1234';

mockGetEnsName.mockRejectedValue(new Error('This is an error'));

await expect(ensNameAction(walletAddress)).rejects.toThrow('This is an error');
});
});
});
22 changes: 5 additions & 17 deletions src/identity/hooks/useName.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import { publicClient } from '../../network/client';
import type { Address, GetEnsNameReturnType } from 'viem';
import { useQuery } from '@tanstack/react-query';

/**
* An asynchronous function to fetch the Ethereum Name Service (ENS) name for a given Ethereum address.
* It returns the ENS name if it exists, or null if it doesn't or in case of an error.
*
* @param address - The Ethereum address for which the ENS name is being fetched.
* @returns A promise that resolves to the ENS name (as a string) or null.
*/
export const ensNameAction = async (address: Address): Promise<GetEnsNameReturnType> => {
return await publicClient.getEnsName({
address,
});
};
import type { Address } from 'viem';
import { GetNameReturnType } from '../types';
import { getName } from '../core/getName';

type UseNameOptions = {
address: Address;
Expand All @@ -38,10 +26,10 @@ type UseNameQueryOptions = {
export const useName = ({ address }: UseNameOptions, queryOptions?: UseNameQueryOptions) => {
const { enabled = true, cacheTime } = queryOptions ?? {};
const ensActionKey = `ens-name-${address}`;
return useQuery<GetEnsNameReturnType>({
return useQuery<GetNameReturnType>({
queryKey: ['useName', ensActionKey],
queryFn: async () => {
return await ensNameAction(address);
return await getName(address);
},
gcTime: cacheTime,
enabled,
Expand Down
4 changes: 4 additions & 0 deletions src/identity/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 🌲☀️🌲
export { Avatar } from './components/Avatar';
export { Name } from './components/Name';
export { getAvatar } from './core/getAvatar';
export { getName } from './core/getName';
export { useAvatar } from './hooks/useAvatar';
export { useName } from './hooks/useName';
export { getEASAttestations } from './getEASAttestations';
Expand All @@ -9,4 +11,6 @@ export type {
EASAttestation,
EASChainDefinition,
GetEASAttestationsOptions,
GetAvatarReturnType,
GetNameReturnType,
} from './types';
Loading

0 comments on commit b795268

Please sign in to comment.