From 867021120305b068ba0cf196b7cd3eba793c4c94 Mon Sep 17 00:00:00 2001 From: Adam Lessey Date: Mon, 14 Oct 2024 08:03:05 -0400 Subject: [PATCH] nft api --- src/api/buildMintTransaction.test.ts | 73 +++++++++++++++++++++++ src/api/buildMintTransaction.ts | 42 ++++++++++++++ src/api/getMintDetails.test.ts | 87 ++++++++++++++++++++++++++++ src/api/getMintDetails.ts | 38 ++++++++++++ src/api/getTokenDetails.test.ts | 83 ++++++++++++++++++++++++++ src/api/getTokenDetails.ts | 38 ++++++++++++ src/api/types.ts | 68 ++++++++++++++++++++++ src/network/definitions/nft.ts | 3 + src/nft/types.ts | 4 ++ 9 files changed, 436 insertions(+) create mode 100644 src/api/buildMintTransaction.test.ts create mode 100644 src/api/buildMintTransaction.ts create mode 100644 src/api/getMintDetails.test.ts create mode 100644 src/api/getMintDetails.ts create mode 100644 src/api/getTokenDetails.test.ts create mode 100644 src/api/getTokenDetails.ts create mode 100644 src/network/definitions/nft.ts create mode 100644 src/nft/types.ts diff --git a/src/api/buildMintTransaction.test.ts b/src/api/buildMintTransaction.test.ts new file mode 100644 index 00000000000..dcdb25dc4e1 --- /dev/null +++ b/src/api/buildMintTransaction.test.ts @@ -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 minting token', + 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 minting token', + }); + expect(mockSendRequest).toHaveBeenCalledWith(CDP_MINT_TOKEN, [params]); + }); +}); diff --git a/src/api/buildMintTransaction.ts b/src/api/buildMintTransaction.ts new file mode 100644 index 00000000000..60db4d5ea7c --- /dev/null +++ b/src/api/buildMintTransaction.ts @@ -0,0 +1,42 @@ +import { CDP_MINT_TOKEN } from '../network/definitions/nft'; +import { sendRequest } from '../network/request'; +import type { BuildMintTransactionParams, BuildMintTransactionResponse } from './types'; + +/** + * Retrieves token details for an NFT contract and token ID + */ +export async function buildMintTransaction({ + mintAddress, + network, + quantity, + takerAddress, +}: BuildMintTransactionParams): Promise { + try { + const res = await sendRequest( + CDP_MINT_TOKEN, + [ + { + mintAddress, + network, + quantity, + takerAddress, + }, + ], + ); + if (res.error) { + return { + code: `${res.error.code}`, + error: 'Error minting token', + message: res.error.message, + }; + } + + return res.result; + } catch (_error) { + return { + code: 'uncaught-nft', + error: 'Something went wrong', + message: 'Error minting token', + }; + } +} diff --git a/src/api/getMintDetails.test.ts b/src/api/getMintDetails.test.ts new file mode 100644 index 00000000000..6e763b2e2b4 --- /dev/null +++ b/src/api/getMintDetails.test.ts @@ -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, + ]); + }); +}); diff --git a/src/api/getMintDetails.ts b/src/api/getMintDetails.ts new file mode 100644 index 00000000000..a050c04589c --- /dev/null +++ b/src/api/getMintDetails.ts @@ -0,0 +1,38 @@ +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, +}: GetMintDetailsParams): Promise { + try { + const res = await sendRequest( + CDP_GET_MINT_DETAILS, + [ + { + contractAddress, + takerAddress, + }, + ], + ); + 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', + }; + } +} diff --git a/src/api/getTokenDetails.test.ts b/src/api/getTokenDetails.test.ts new file mode 100644 index 00000000000..afe89b129bc --- /dev/null +++ b/src/api/getTokenDetails.test.ts @@ -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', + amountUUSD: '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, + ]); + }); +}); diff --git a/src/api/getTokenDetails.ts b/src/api/getTokenDetails.ts new file mode 100644 index 00000000000..06bd446177a --- /dev/null +++ b/src/api/getTokenDetails.ts @@ -0,0 +1,38 @@ +import { CDP_GET_TOKEN_DETAILS } from '../network/definitions/nft'; +import { sendRequest } from '../network/request'; +import type { GetTokenDetailsParams, GetTokenDetailsResponse } from './types'; + +/** + * Retrieves token details for an NFT contract and token ID + */ +export async function getTokenDetails({ + contractAddress, + tokenId, +}: GetTokenDetailsParams): Promise { + try { + const res = await sendRequest< + GetTokenDetailsParams, + GetTokenDetailsResponse + >(CDP_GET_TOKEN_DETAILS, [ + { + contractAddress, + tokenId, + }, + ]); + if (res.error) { + return { + code: `${res.error.code}`, + error: 'Error fetching token details', + message: res.error.message, + }; + } + + return res.result; + } catch (_error) { + return { + code: 'uncaught-nft', + error: 'Something went wrong', + message: 'Error fetching token details', + }; + } +} diff --git a/src/api/types.ts b/src/api/types.ts index 99eabfd738a..f0ef365d104 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,4 +1,5 @@ import type { Address } from 'viem'; +import type { ContractType } from '../nft/types'; import type { Fee, QuoteWarning, SwapQuote, Transaction } from '../swap/types'; import type { Token } from '../token/types'; @@ -143,3 +144,70 @@ export type RawTransactionData = { }; export type SwapAPIParams = GetQuoteAPIParams | GetSwapAPIParams; + +export type GetTokenDetailsParams = { + contractAddress: Address; // The address of the token contract + tokenId?: string; // The ID of the token +}; + +type TokenDetails = { + name: string; + description: string; + imageUrl: string; + animationUrl: string; + mimeType: string; + ownerAddress: Address; + lastSoldPrice: { + amount: string; + currency: string; + amountUSD: string; + }; + contractType: ContractType; // ERC721, ERC1155 +}; + +export type GetTokenDetailsResponse = TokenDetails | APIError; + +export type GetMintDetailsParams = { + contractAddress: Address; // The address of the token contract + takerAddress: Address; // The address of the user +}; + +type MintDetails = { + price: { + amount: string; + currency: string; + amountUSD: string; + }; + mintFee: { + amount: string; + currency: string; + amountUSD: string; + }; + maxMintsPerWallet: number; + isEligibleToMint: boolean; + creatorAddress: Address; + totalTokens: string; + totalOwners: string; + network: string; +}; + +export type GetMintDetailsResponse = MintDetails | APIError; + +export type BuildMintTransactionParams = { + mintAddress: Address; // The address of the token contract to mint + network: string; // The network the mint contract is on + quantity: number; // The number of tokens to mint + takerAddress: Address; // The address of the user + tokenId?: string; // The ID of the token, required for ERC1155 +}; + +type MintTransaction = { + callData: { + data: string; + to: string; + from: string; + value: string; + }; +}; + +export type BuildMintTransactionResponse = MintTransaction | APIError; diff --git a/src/network/definitions/nft.ts b/src/network/definitions/nft.ts new file mode 100644 index 00000000000..43f1a497570 --- /dev/null +++ b/src/network/definitions/nft.ts @@ -0,0 +1,3 @@ +export const CDP_GET_TOKEN_DETAILS = 'cdp_getTokenDetails'; +export const CDP_GET_MINT_DETAILS = 'cdp_getMintDetails'; +export const CDP_MINT_TOKEN = 'cdp_mintToken'; diff --git a/src/nft/types.ts b/src/nft/types.ts new file mode 100644 index 00000000000..87be8ef0542 --- /dev/null +++ b/src/nft/types.ts @@ -0,0 +1,4 @@ +export enum ContractType { + ERC721 = 'ERC721', + ERC1155 = 'ERC1155', +}