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

nft api #1409

Merged
merged 18 commits into from
Nov 1, 2024
5 changes: 5 additions & 0 deletions .changeset/two-glasses-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@coinbase/onchainkit': patch
---

-feat: Add NFTCard and NFTMintCard components. By @alessey #1409
2 changes: 0 additions & 2 deletions playground/nextjs-app-router/components/demo/NFTCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useReservoirNFTData } from '@/lib/nft/useReservoirNFTData';
import {
type LifecycleStatus,
NFTCard,
Expand Down Expand Up @@ -42,7 +41,6 @@ function NFTCardDemo() {
<NFTCard
contractAddress={contractAddress}
tokenId={tokenId}
useNFTData={useReservoirNFTData}
onStatus={handleOnStatus}
onSuccess={handleOnSuccess}
onError={handleOnError}
Expand Down
6 changes: 1 addition & 5 deletions playground/nextjs-app-router/components/demo/NFTMintCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { buildMintTransaction } from '@/lib/nft/buildMintTransaction';
import { useReservoirMintData } from '@/lib/nft/useReservoirMintData';
import {
type LifecycleStatus,
type NFTError,
Expand All @@ -23,7 +21,7 @@ function NFTMintCardDemo() {
const { nftToken, isSponsored } = useContext(AppContext);

const [contractAddress, tokenId] = (
nftToken ?? '0x1D6b183bD47F914F9f1d3208EDCF8BefD7F84E63:2'
nftToken ?? '0x44dF55B47F24B73190657fE9107Ca43234bbc21E'
).split(':') as [`0x${string}`, string];

const handleOnStatus = useCallback((lifecycleStatus: LifecycleStatus) => {
Expand All @@ -45,8 +43,6 @@ function NFTMintCardDemo() {
<NFTMintCard
contractAddress={contractAddress}
tokenId={tokenId}
useNFTData={useReservoirMintData}
buildMintTransaction={buildMintTransaction}
isSponsored={isSponsored}
onStatus={handleOnStatus}
onSuccess={handleOnSuccess}
Expand Down
2 changes: 1 addition & 1 deletion playground/nextjs-app-router/lib/nft/testAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
// ink base - 0xff93d3f49ec467B61BF61D42DDdCFDbA5c732446 ERC721
// yellow digital meltdown 0x19Ef6FdBddd2752fBA1B419cD43a1Fab5b0101Db ERC721
// base around the world 0xFE12c6d7555B4A94B352998f8e1E96aDFbE628e9 ERC721

// dryad (long name single line) 0x6d65115def07d25841891d0679189f2c42032f48 ERC721
73 changes: 73 additions & 0 deletions src/api/buildMintTransaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { type Mock, describe, expect, it, vi } from 'vitest';
import { CDP_MINT_TOKEN } from '../network/definitions/nft';
import { sendRequest } from '../network/request';
import { buildMintTransaction } from './buildMintTransaction';
import type { BuildMintTransactionParams } from './types';

vi.mock('../network/request', () => ({
sendRequest: vi.fn(),
}));

describe('buildMintTransaction', () => {
const mockSendRequest = sendRequest as Mock;

const params: BuildMintTransactionParams = {
mintAddress: '0x123',
network: 'networks/base-mainnet',
quantity: 1,
takerAddress: '0x456',
};

it('should return call data when request is successful', async () => {
const mockResponse = {
result: {
callData: {
to: '0x123',
from: '0x456',
data: '0x789',
value: '1',
},
},
};

mockSendRequest.mockResolvedValueOnce(mockResponse);

const result = await buildMintTransaction(params);

expect(result).toEqual(mockResponse.result);
expect(mockSendRequest).toHaveBeenCalledWith(CDP_MINT_TOKEN, [params]);
});

it('should return error details when request fails with an error', async () => {
const mockErrorResponse = {
error: {
code: '404',
message: 'Not Found',
},
};

mockSendRequest.mockResolvedValueOnce(mockErrorResponse);

const result = await buildMintTransaction(params);

expect(result).toEqual({
code: '404',
error: 'Error building mint transaction',
message: 'Not Found',
});
expect(mockSendRequest).toHaveBeenCalledWith(CDP_MINT_TOKEN, [params]);
});

it('should return uncaught error details when an exception is thrown', async () => {
mockSendRequest.mockRejectedValue(new Error('Network Error'));

const result = await buildMintTransaction(params);

expect(result).toEqual({
code: 'uncaught-nft',
error: 'Something went wrong',
message: 'Error building mint transaction',
});
expect(mockSendRequest).toHaveBeenCalledWith(CDP_MINT_TOKEN, [params]);
});
});
47 changes: 47 additions & 0 deletions src/api/buildMintTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { CDP_MINT_TOKEN } from '../network/definitions/nft';
import { sendRequest } from '../network/request';
import type {
BuildMintTransactionParams,
BuildMintTransactionResponse,
} from './types';

/**
* Retrieves contract to mint an nft
*/
export async function buildMintTransaction({
mintAddress,
tokenId,
network = '',
quantity,
takerAddress,
}: BuildMintTransactionParams): Promise<BuildMintTransactionResponse> {
try {
const res = await sendRequest<
BuildMintTransactionParams,
BuildMintTransactionResponse
>(CDP_MINT_TOKEN, [
{
mintAddress,
network,
quantity,
takerAddress,
tokenId,
},
]);
if (res.error) {
return {
code: `${res.error.code}`,
error: 'Error building mint transaction',
message: res.error.message,
};
}

return res.result;
} catch (_error) {
return {
code: 'uncaught-nft',
error: 'Something went wrong',
message: 'Error building mint transaction',
};
}
}
87 changes: 87 additions & 0 deletions src/api/getMintDetails.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { type Mock, describe, expect, it, vi } from 'vitest';
import { CDP_GET_MINT_DETAILS } from '../network/definitions/nft';
import { sendRequest } from '../network/request';
import { getMintDetails } from './getMintDetails';
import type { GetMintDetailsParams } from './types';

vi.mock('../network/request', () => ({
sendRequest: vi.fn(),
}));

describe('getMintDetails', () => {
const mockSendRequest = sendRequest as Mock;

const params: GetMintDetailsParams = {
contractAddress: '0x123',
takerAddress: '0x456',
};

it('should return mint details when request is successful', async () => {
const mockResponse = {
result: {
price: {
amount: '1',
currency: 'ETH',
amountUsd: '2000',
},
fee: {
amount: '0.1',
currency: 'ETH',
amountUsd: '200',
},
maxMintsPerWallet: 3,
isEligibleToMint: true,
creatorAddress: '0x123',
totalTokens: '10',
totalOwners: '5',
network: 'networks/base-mainnet',
},
};

mockSendRequest.mockResolvedValueOnce(mockResponse);

const result = await getMintDetails(params);

expect(result).toEqual(mockResponse.result);
expect(mockSendRequest).toHaveBeenCalledWith(CDP_GET_MINT_DETAILS, [
params,
]);
});

it('should return error details when request fails with an error', async () => {
const mockErrorResponse = {
error: {
code: '404',
message: 'Not Found',
},
};

mockSendRequest.mockResolvedValueOnce(mockErrorResponse);

const result = await getMintDetails(params);

expect(result).toEqual({
code: '404',
error: 'Error fetching mint details',
message: 'Not Found',
});
expect(mockSendRequest).toHaveBeenCalledWith(CDP_GET_MINT_DETAILS, [
params,
]);
});

it('should return uncaught error details when an exception is thrown', async () => {
mockSendRequest.mockRejectedValue(new Error('Network Error'));

const result = await getMintDetails(params);

expect(result).toEqual({
code: 'uncaught-nft',
error: 'Something went wrong',
message: 'Error fetching mint details',
});
expect(mockSendRequest).toHaveBeenCalledWith(CDP_GET_MINT_DETAILS, [
params,
]);
});
});
40 changes: 40 additions & 0 deletions src/api/getMintDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { CDP_GET_MINT_DETAILS } from '../network/definitions/nft';
import { sendRequest } from '../network/request';
import type { GetMintDetailsParams, GetMintDetailsResponse } from './types';

/**
* Retrieves mint details for an NFT contract and token ID
*/
export async function getMintDetails({
contractAddress,
takerAddress,
tokenId,
}: GetMintDetailsParams): Promise<GetMintDetailsResponse> {
try {
const res = await sendRequest<GetMintDetailsParams, GetMintDetailsResponse>(
CDP_GET_MINT_DETAILS,
[
{
contractAddress,
takerAddress,
tokenId,
},
],
);
if (res.error) {
return {
code: `${res.error.code}`,
error: 'Error fetching mint details',
message: res.error.message,
};
}

return res.result;
} catch (_error) {
return {
code: 'uncaught-nft',
error: 'Something went wrong',
message: 'Error fetching mint details',
};
}
}
83 changes: 83 additions & 0 deletions src/api/getTokenDetails.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { type Mock, describe, expect, it, vi } from 'vitest';
import { CDP_GET_TOKEN_DETAILS } from '../network/definitions/nft';
import { sendRequest } from '../network/request';
import { getTokenDetails } from './getTokenDetails';
import type { GetTokenDetailsParams } from './types';

vi.mock('../network/request', () => ({
sendRequest: vi.fn(),
}));

describe('getTokenDetails', () => {
const mockSendRequest = sendRequest as Mock;

const params: GetTokenDetailsParams = {
contractAddress: '0x123',
tokenId: '1',
};

it('should return token details when request is successful', async () => {
const mockResponse = {
result: {
name: 'NFT Name',
description: 'NFT Description',
imageUrl: 'https://nft-image-url.com',
animationUrl: 'https://nft-animation-url.com',
mimeType: 'image/png',
ownerAddress: '0x123',
lastSoldPrice: {
amount: '1',
currency: 'ETH',
amountUSD: '2000',
},
contractType: 'ERC721',
},
};

mockSendRequest.mockResolvedValueOnce(mockResponse);

const result = await getTokenDetails(params);

expect(result).toEqual(mockResponse.result);
expect(mockSendRequest).toHaveBeenCalledWith(CDP_GET_TOKEN_DETAILS, [
params,
]);
});

it('should return error details when request fails with an error', async () => {
const mockErrorResponse = {
error: {
code: '404',
message: 'Not Found',
},
};

mockSendRequest.mockResolvedValueOnce(mockErrorResponse);

const result = await getTokenDetails(params);

expect(result).toEqual({
code: '404',
error: 'Error fetching token details',
message: 'Not Found',
});
expect(mockSendRequest).toHaveBeenCalledWith(CDP_GET_TOKEN_DETAILS, [
params,
]);
});

it('should return uncaught error details when an exception is thrown', async () => {
mockSendRequest.mockRejectedValue(new Error('Network Error'));

const result = await getTokenDetails(params);

expect(result).toEqual({
code: 'uncaught-nft',
error: 'Something went wrong',
message: 'Error fetching token details',
});
expect(mockSendRequest).toHaveBeenCalledWith(CDP_GET_TOKEN_DETAILS, [
params,
]);
});
});
Loading
Loading