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: added useAddress and getAddress #1170

Merged
merged 4 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/beige-eagles-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@coinbase/onchainkit": patch
---

ok
27 changes: 1 addition & 26 deletions playground/nextjs-app-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,7 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
bun build:prepare && bun build:link && bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
3 changes: 2 additions & 1 deletion playground/nextjs-app-router/components/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { useConnect, useConnectors } from 'wagmi';
import { WalletPreference } from './form/wallet-type';

export enum OnchainKitComponent {
Transaction = 'transaction',
Identity = 'identity',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding Identity as well, so we can test that too.

Swap = 'swap',
Transaction = 'transaction',
Wallet = 'wallet',
}
export type Paymaster = {
Expand Down
5 changes: 4 additions & 1 deletion playground/nextjs-app-router/components/Demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Chain } from '@/components/form/chain';
import { PaymasterUrl } from '@/components/form/paymaster';
import { WalletType } from '@/components/form/wallet-type';
import { useContext, useEffect } from 'react';
import IdentityDemo from './demo/Identity';
import SwapDemo from './demo/Swap';
import TransactionDemo from './demo/Transaction';
import WalletDemo from './demo/Wallet';
Expand Down Expand Up @@ -37,7 +38,9 @@ function Demo() {
</div>
<div className="flex flex-1 flex-col bg-[linear-gradient(to_right,#f0f0f0_1px,transparent_1px),linear-gradient(to_bottom,#f0f0f0_1px,transparent_1px)] bg-[size:6rem_4rem]">
<div className="flex h-full w-full flex-col justify-center">
{activeComponent === OnchainKitComponent.Transaction ? (
{activeComponent === OnchainKitComponent.Identity ? (
<IdentityDemo />
) : activeComponent === OnchainKitComponent.Transaction ? (
<TransactionDemo />
) : activeComponent === OnchainKitComponent.Swap ? (
<SwapDemo />
Expand Down
64 changes: 64 additions & 0 deletions playground/nextjs-app-router/components/demo/Identity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
Avatar,
Name,
useAddress,
useAvatar,
useName,
} from '@coinbase/onchainkit/identity';
import { useEffect } from 'react';
import { base } from 'viem/chains';

const demoAddress = '0x02feeb0AdE57b6adEEdE5A4EEea6Cf8c21BeB6B1';

export default function IdentityDemo() {
const { data: address } = useAddress({
name: 'zizzamia.eth',
});
const { data: addressBasename } = useAddress({
name: 'zizzamia.base.eth',
});
const { data: avatar } = useAvatar({
ensName: 'zizzamia.eth',
});
const { data: avatarBasename } = useAvatar({
ensName: 'zizzamia.eth',
chain: base,
});
const { data: name } = useName({ address: demoAddress });
const { data: basename } = useName({
address: demoAddress,
chain: base,
});

useEffect(() => {
console.log('useAddress default', address);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing both the components and the utilities.

console.log('useAddress base', addressBasename);
console.log('useAvatar default', avatar);
console.log('useAvatar base', avatarBasename);
console.log('useName default', name);
console.log('useName base', basename);
}, [address, addressBasename, avatar, avatarBasename, name, basename]);

return (
<div className="mx-auto">
<div className="relative h-full w-full">
<ul>
<li>
<Avatar address={demoAddress} />
</li>
<li>
<Avatar address={demoAddress} chain={base} />
</li>
</ul>
<ul>
<li>
<Name address={demoAddress} />
</li>
<li>
<Name address={demoAddress} chain={base} />
</li>
</ul>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function ActiveComponent() {
<SelectValue placeholder="Select component" />
</SelectTrigger>
<SelectContent>
<SelectItem value={OnchainKitComponent.Identity}>Identity</SelectItem>
<SelectItem value={OnchainKitComponent.Transaction}>
Transaction
</SelectItem>
Expand Down
81 changes: 81 additions & 0 deletions src/identity/hooks/useAddress.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { renderHook, waitFor } from '@testing-library/react';
import { base, baseSepolia, mainnet } from 'viem/chains';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { publicClient } from '../../network/client';
import { getChainPublicClient } from '../../network/getChainPublicClient';
import { getNewReactQueryTestProvider } from './getNewReactQueryTestProvider';
import { useAddress } from './useAddress';

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

vi.mock('../../network/getChainPublicClient', () => ({
...vi.importActual('../../network/getChainPublicClient'),
getChainPublicClient: vi.fn(() => publicClient),
}));

describe('useAddress', () => {
const mockGetEnsAddress = publicClient.getEnsAddress as vi.Mock;

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

it('should return the correct address and loading state', async () => {
const testEnsName = 'test.ens';
const testEnsAddress = '0x1234';
mockGetEnsAddress.mockResolvedValue(testEnsAddress);
const { result } = renderHook(() => useAddress({ name: testEnsName }), {
wrapper: getNewReactQueryTestProvider(),
});
await waitFor(() => {
expect(result.current.data).toBe(testEnsAddress);
expect(result.current.isLoading).toBe(false);
});
expect(getChainPublicClient).toHaveBeenCalledWith(mainnet);
});

it('should return the loading state true while still fetching ENS address', async () => {
const testEnsName = 'test.ens';
const { result } = renderHook(() => useAddress({ name: testEnsName }), {
wrapper: getNewReactQueryTestProvider(),
});
await waitFor(() => {
expect(result.current.data).toBe(undefined);
expect(result.current.isLoading).toBe(true);
});
});

it('should return correct base mainnet address', async () => {
const testEnsName = 'shrek.base.eth';
const testEnsAddress = '0x1234';
mockGetEnsAddress.mockResolvedValue(testEnsAddress);
const { result } = renderHook(
() => useAddress({ name: testEnsName, chain: base }),
{
wrapper: getNewReactQueryTestProvider(),
},
);
await waitFor(() => {
expect(result.current.data).toBe(testEnsAddress);
expect(result.current.isLoading).toBe(false);
});
expect(getChainPublicClient).toHaveBeenCalledWith(base);
});

it('should return correct base sepolia address', async () => {
const testEnsName = 'shrek.basetest.eth';
const testEnsAddress = '0x1234';
mockGetEnsAddress.mockResolvedValue(testEnsAddress);
const { result } = renderHook(
() => useAddress({ name: testEnsName, chain: baseSepolia }),
{
wrapper: getNewReactQueryTestProvider(),
},
);
await waitFor(() => {
expect(result.current.data).toBe(testEnsAddress);
expect(result.current.isLoading).toBe(false);
});
expect(getChainPublicClient).toHaveBeenCalledWith(baseSepolia);
});
});
25 changes: 25 additions & 0 deletions src/identity/hooks/useAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useQuery } from '@tanstack/react-query';
import { mainnet } from 'viem/chains';
import type {
GetAddressReturnType,
UseAddressOptions,
UseQueryOptions,
} from '../types';
import { getAddress } from '../utils/getAddress';

export const useAddress = (
{ name, chain = mainnet }: UseAddressOptions,
queryOptions?: UseQueryOptions,
) => {
const { enabled = true, cacheTime } = queryOptions ?? {};
const actionKey = `useAddress-${name}-${chain.id}`;
return useQuery<GetAddressReturnType>({
queryKey: ['useAddress', actionKey],
queryFn: async () => {
return await getAddress({ name, chain });
},
gcTime: cacheTime,
enabled,
refetchOnWindowFocus: false,
});
};
5 changes: 2 additions & 3 deletions src/identity/hooks/useAvatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mainnet } from 'viem/chains';
import type {
GetAvatarReturnType,
UseAvatarOptions,
UseAvatarQueryOptions,
UseQueryOptions,
} from '../types';
import { getAvatar } from '../utils/getAvatar';

Expand All @@ -12,11 +12,10 @@ import { getAvatar } from '../utils/getAvatar';
*/
export const useAvatar = (
{ ensName, chain = mainnet }: UseAvatarOptions,
queryOptions?: UseAvatarQueryOptions,
queryOptions?: UseQueryOptions,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplifing the type

) => {
const { enabled = true, cacheTime } = queryOptions ?? {};
const ensActionKey = `ens-avatar-${ensName}-${chain.id}`;

return useQuery<GetAvatarReturnType>({
queryKey: ['useAvatar', ensActionKey],
queryFn: async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/identity/hooks/useName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mainnet } from 'viem/chains';
import type {
GetNameReturnType,
UseNameOptions,
UseNameQueryOptions,
UseQueryOptions,
} from '../types';
import { getName } from '../utils/getName';

Expand All @@ -15,7 +15,7 @@ import { getName } from '../utils/getName';
*/
export const useName = (
{ address, chain = mainnet }: UseNameOptions,
queryOptions?: UseNameQueryOptions,
queryOptions?: UseQueryOptions,
) => {
const { enabled = true, cacheTime } = queryOptions ?? {};
const ensActionKey = `ens-name-${address}-${chain.id}`;
Expand Down
5 changes: 3 additions & 2 deletions src/identity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export { Badge } from './components/Badge';
export { EthBalance } from './components/EthBalance';
export { Identity } from './components/Identity';
export { Name } from './components/Name';
export { isBasename } from './utils/isBasename';
export { getAttestations } from './utils/getAttestations';
export { getAvatar } from './utils/getAvatar';
export { getName } from './utils/getName';
export { useAddress } from './hooks/useAddress';
export { useAttestations } from './hooks/useAttestations';
export { useAvatar } from './hooks/useAvatar';
export { useName } from './hooks/useName';
Expand All @@ -31,7 +33,6 @@ export type {
IdentityReact,
NameReact,
UseAvatarOptions,
UseAvatarQueryOptions,
UseQueryOptions,
UseNameOptions,
UseNameQueryOptions,
} from './types';
32 changes: 22 additions & 10 deletions src/identity/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ export type GetAvatar = {
*/
export type GetAvatarReturnType = string | null;

/**
* Note: exported as public Type
*/
export type GetAddress = {
name: string | Basename;
chain?: Chain;
};

/**
* Note: exported as public Type
*/
Expand All @@ -123,6 +131,11 @@ export type GetName = {
chain?: Chain;
};

/**
* Note: exported as public Type
*/
export type GetAddressReturnType = Address | null;

/**
* Note: exported as public Type
*/
Expand Down Expand Up @@ -174,6 +187,14 @@ export type UseAttestations = {
schemaId: Address | null;
};

/**
* Note: exported as public Type
*/
export type UseAddressOptions = {
name: string | Basename; // The ENS or Basename for which the Ethereum address is to be fetched
chain?: Chain; // Optional chain for domain resolution
};

/**
* Note: exported as public Type
*/
Expand All @@ -185,7 +206,7 @@ export type UseAvatarOptions = {
/**
* Note: exported as public Type
*/
export type UseAvatarQueryOptions = {
export type UseQueryOptions = {
enabled?: boolean;
cacheTime?: number;
};
Expand All @@ -197,12 +218,3 @@ export type UseNameOptions = {
address: Address; // The Ethereum address for which the ENS name is to be fetched.
chain?: Chain; // Optional chain for domain resolution
};

/**
* Note: exported as public Type
* Additional query options, including `enabled` and `cacheTime`
*/
export type UseNameQueryOptions = {
enabled?: boolean; // Whether the query should be enabled. Defaults to true.
cacheTime?: number; // Cache time in milliseconds.
};
Loading