diff --git a/apps/web/app/(basenames)/api/proofs/baseEthHolders/route.test.ts b/apps/web/app/(basenames)/api/proofs/baseEthHolders/route.test.ts deleted file mode 100644 index 887fc17c258..00000000000 --- a/apps/web/app/(basenames)/api/proofs/baseEthHolders/route.test.ts +++ /dev/null @@ -1,348 +0,0 @@ -/** - * @jest-environment node - */ -import { NextRequest } from 'next/server'; -import { GET } from './route'; -import { base, baseSepolia } from 'viem/chains'; - -type ErrorResponse = { error: string }; - -type SuccessResponse = { - address: string; - namespace: string; - proofs: string[]; - discountValidatorAddress: string; -}; - -// Mock dependencies -jest.mock('apps/web/src/utils/proofs', () => { - class MockProofsException extends Error { - public statusCode: number; - - constructor(message: string, statusCode: number) { - super(message); - this.name = 'ProofsException'; - this.statusCode = statusCode; - } - } - - return { - proofValidation: jest.fn(), - getWalletProofs: jest.fn(), - ProofTableNamespace: { - BaseEthHolders: 'basenames_base_eth_holders_discount', - }, - ProofsException: MockProofsException, - }; -}); - -jest.mock('apps/web/src/utils/logger', () => ({ - logger: { - error: jest.fn(), - }, -})); - -import { - proofValidation, - getWalletProofs, - ProofsException, -} from 'apps/web/src/utils/proofs'; - -const mockProofValidation = proofValidation as jest.Mock; -const mockGetWalletProofs = getWalletProofs as jest.Mock; -const MockProofsException = ProofsException; - -describe('baseEthHolders route', () => { - const validAddress = '0x1234567890123456789012345678901234567890'; - const validChain = base.id.toString(); - - beforeEach(() => { - jest.clearAllMocks(); - mockProofValidation.mockReturnValue(undefined); - }); - - describe('GET', () => { - it('should return 405 when method is not GET', async () => { - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - { method: 'POST' }, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(405); - expect(data).toEqual({ error: 'method not allowed' }); - }); - - it('should return 400 when address validation fails', async () => { - mockProofValidation.mockReturnValue({ - error: 'A single valid address is required', - status: 400, - }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=invalid&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'A single valid address is required' }); - }); - - it('should return 400 when chain validation fails', async () => { - mockProofValidation.mockReturnValue({ error: 'invalid chain', status: 400 }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=invalid`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'invalid chain' }); - }); - - it('should return 400 when chain is not Base or Base Sepolia', async () => { - mockProofValidation.mockReturnValue({ - error: 'chain must be Base or Base Sepolia', - status: 400, - }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=1`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'chain must be Base or Base Sepolia' }); - }); - - it('should return successful response with proofs for valid request', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'basenames_base_eth_holders_discount', - proofs: [ - '0x56ce3bbc909b90035ae373d32c56a9d81d26bb505dd935cdee6afc384bcaed8d', - '0x99e940ed9482bf59ba5ceab7df0948798978a1acaee0ecb41f64fe7f40eedd17', - ], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as SuccessResponse; - - expect(response.status).toBe(200); - expect(data).toEqual(mockResponse); - expect(mockGetWalletProofs).toHaveBeenCalledWith( - validAddress, - base.id, - 'basenames_base_eth_holders_discount', - ); - }); - - it('should return successful response for Base Sepolia chain', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'basenames_base_eth_holders_discount', - proofs: ['0xproof1', '0xproof2'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${baseSepolia.id.toString()}`, - ); - - const response = await GET(request); - const data = (await response.json()) as SuccessResponse; - - expect(response.status).toBe(200); - expect(data).toEqual(mockResponse); - expect(mockGetWalletProofs).toHaveBeenCalledWith( - validAddress, - baseSepolia.id, - 'basenames_base_eth_holders_discount', - ); - }); - - it('should return 409 when address has already claimed a username', async () => { - mockGetWalletProofs.mockRejectedValue( - new MockProofsException('This address has already claimed a username.', 409), - ); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(409); - expect(data).toEqual({ error: 'This address has already claimed a username.' }); - }); - - it('should return 404 when address is not eligible for base eth holders discount', async () => { - mockGetWalletProofs.mockRejectedValue( - new MockProofsException( - 'address is not eligible for [basenames_base_eth_holders_discount] this discount.', - 404, - ), - ); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(404); - expect(data).toEqual({ - error: 'address is not eligible for [basenames_base_eth_holders_discount] this discount.', - }); - }); - - it('should return 500 when an unexpected error occurs', async () => { - mockGetWalletProofs.mockRejectedValue(new Error('Unexpected error')); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(500); - expect(data).toEqual({ error: 'An unexpected error occurred' }); - }); - - it('should call proofValidation with correct parameters', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'basenames_base_eth_holders_discount', - proofs: ['0xproof'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - ); - - await GET(request); - - expect(mockProofValidation).toHaveBeenCalledWith(validAddress, validChain); - }); - - it('should return response with empty proofs array when no proofs exist', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'basenames_base_eth_holders_discount', - proofs: [], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as SuccessResponse; - - expect(response.status).toBe(200); - expect(data).toEqual(mockResponse); - expect(data.proofs).toEqual([]); - }); - - it('should handle missing address parameter', async () => { - mockProofValidation.mockReturnValue({ - error: 'A single valid address is required', - status: 400, - }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'A single valid address is required' }); - }); - - it('should handle missing chain parameter', async () => { - mockProofValidation.mockReturnValue({ - error: 'invalid chain', - status: 400, - }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'invalid chain' }); - }); - - it('should pass address directly to getWalletProofs without modification', async () => { - const mixedCaseAddress = '0xAbCdEf1234567890123456789012345678901234'; - const mockResponse: SuccessResponse = { - address: mixedCaseAddress, - namespace: 'basenames_base_eth_holders_discount', - proofs: ['0xproof'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${mixedCaseAddress}&chain=${validChain}`, - ); - - await GET(request); - - expect(mockGetWalletProofs).toHaveBeenCalledWith( - mixedCaseAddress, - base.id, - 'basenames_base_eth_holders_discount', - ); - }); - - it('should call getWalletProofs with BaseEthHolders namespace', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'basenames_base_eth_holders_discount', - proofs: ['0xproof'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/baseEthHolders?address=${validAddress}&chain=${validChain}`, - ); - - await GET(request); - - expect(mockGetWalletProofs).toHaveBeenCalledWith( - expect.any(String), - expect.any(Number), - 'basenames_base_eth_holders_discount', - ); - }); - }); -}); diff --git a/apps/web/app/(basenames)/api/proofs/baseEthHolders/route.ts b/apps/web/app/(basenames)/api/proofs/baseEthHolders/route.ts deleted file mode 100644 index 92f17e3f753..00000000000 --- a/apps/web/app/(basenames)/api/proofs/baseEthHolders/route.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { withTimeout } from 'apps/web/app/api/decorators'; -import { logger } from 'apps/web/src/utils/logger'; -import { - getWalletProofs, - ProofsException, - ProofTableNamespace, - proofValidation, -} from 'apps/web/src/utils/proofs'; - -/* -this endpoint returns whether or not the account has a base eth nft -if result array is empty, user has no base eth nft -example return: -{ - "address": "0xB18e4C959bccc8EF86D78DC297fb5efA99550d85", - "proofs": "[0x56ce3bbc909b90035ae373d32c56a9d81d26bb505dd935cdee6afc384bcaed8d, 0x99e940ed9482bf59ba5ceab7df0948798978a1acaee0ecb41f64fe7f40eedd17]" - "discountValidatorAddress": "0x..." -} -*/ -async function handler(req: NextRequest) { - if (req.method !== 'GET') { - return NextResponse.json({ error: 'method not allowed' }, { status: 405 }); - } - const address = req.nextUrl.searchParams.get('address'); - const chain = req.nextUrl.searchParams.get('chain'); - const validationErr = proofValidation(address ?? '', chain ?? ''); - if (validationErr) { - return NextResponse.json({ error: validationErr.error }, { status: validationErr.status }); - } - - try { - const responseData = await getWalletProofs( - address as `0x${string}`, - parseInt(chain as string), - ProofTableNamespace.BaseEthHolders, - ); - - return NextResponse.json(responseData); - } catch (error: unknown) { - if (error instanceof ProofsException) { - return NextResponse.json({ error: error.message }, { status: error.statusCode }); - } - logger.error('error getting proofs for baseEthHolders', error); - } - - // If error is not an instance of Error, return a generic error message - return NextResponse.json({ error: 'An unexpected error occurred' }, { status: 500 }); -} - -export const GET = withTimeout(handler); diff --git a/apps/web/app/(basenames)/api/proofs/bns/route.test.ts b/apps/web/app/(basenames)/api/proofs/bns/route.test.ts deleted file mode 100644 index f21e125160b..00000000000 --- a/apps/web/app/(basenames)/api/proofs/bns/route.test.ts +++ /dev/null @@ -1,333 +0,0 @@ -/** - * @jest-environment node - */ -import { NextRequest } from 'next/server'; -import { GET } from './route'; -import { base, baseSepolia } from 'viem/chains'; - -type ErrorResponse = { error: string }; - -type SuccessResponse = { - address: string; - namespace: string; - proofs: string[]; - discountValidatorAddress: string; -}; - -// Mock dependencies -jest.mock('apps/web/src/utils/proofs', () => { - class MockProofsException extends Error { - public statusCode: number; - - constructor(message: string, statusCode: number) { - super(message); - this.name = 'ProofsException'; - this.statusCode = statusCode; - } - } - - return { - proofValidation: jest.fn(), - getWalletProofs: jest.fn(), - ProofTableNamespace: { - BNSDiscount: 'bns_discount', - }, - ProofsException: MockProofsException, - }; -}); - -jest.mock('apps/web/src/utils/logger', () => ({ - logger: { - error: jest.fn(), - }, -})); - -import { - proofValidation, - getWalletProofs, - ProofsException, -} from 'apps/web/src/utils/proofs'; - -const mockProofValidation = proofValidation as jest.Mock; -const mockGetWalletProofs = getWalletProofs as jest.Mock; -const MockProofsException = ProofsException; - -describe('bns route', () => { - const validAddress = '0x1234567890123456789012345678901234567890'; - const validChain = base.id.toString(); - - beforeEach(() => { - jest.clearAllMocks(); - mockProofValidation.mockReturnValue(undefined); - }); - - describe('GET', () => { - it('should return 400 when address validation fails', async () => { - mockProofValidation.mockReturnValue({ - error: 'A single valid address is required', - status: 400, - }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=invalid&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'A single valid address is required' }); - }); - - it('should return 400 when chain validation fails', async () => { - mockProofValidation.mockReturnValue({ error: 'invalid chain', status: 400 }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=invalid`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'invalid chain' }); - }); - - it('should return 400 when chain is not Base or Base Sepolia', async () => { - mockProofValidation.mockReturnValue({ - error: 'chain must be Base or Base Sepolia', - status: 400, - }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=1`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'chain must be Base or Base Sepolia' }); - }); - - it('should return successful response with proofs for valid request', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'bns_discount', - proofs: [ - '0x56ce3bbc909b90035ae373d32c56a9d81d26bb505dd935cdee6afc384bcaed8d', - '0x99e940ed9482bf59ba5ceab7df0948798978a1acaee0ecb41f64fe7f40eedd17', - ], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as SuccessResponse; - - expect(response.status).toBe(200); - expect(data).toEqual(mockResponse); - expect(mockGetWalletProofs).toHaveBeenCalledWith( - validAddress, - base.id, - 'bns_discount', - ); - }); - - it('should return successful response for Base Sepolia chain', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'bns_discount', - proofs: ['0xproof1', '0xproof2'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${baseSepolia.id.toString()}`, - ); - - const response = await GET(request); - const data = (await response.json()) as SuccessResponse; - - expect(response.status).toBe(200); - expect(data).toEqual(mockResponse); - expect(mockGetWalletProofs).toHaveBeenCalledWith( - validAddress, - baseSepolia.id, - 'bns_discount', - ); - }); - - it('should return 409 when address has already claimed a username', async () => { - mockGetWalletProofs.mockRejectedValue( - new MockProofsException('This address has already claimed a username.', 409), - ); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(409); - expect(data).toEqual({ error: 'This address has already claimed a username.' }); - }); - - it('should return 404 when address is not eligible for bns discount', async () => { - mockGetWalletProofs.mockRejectedValue( - new MockProofsException( - 'address is not eligible for [bns_discount] this discount.', - 404, - ), - ); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(404); - expect(data).toEqual({ - error: 'address is not eligible for [bns_discount] this discount.', - }); - }); - - it('should return 500 when an unexpected error occurs', async () => { - mockGetWalletProofs.mockRejectedValue(new Error('Unexpected error')); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(500); - expect(data).toEqual({ error: 'An unexpected error occurred' }); - }); - - it('should call proofValidation with correct parameters', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'bns_discount', - proofs: ['0xproof'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${validChain}`, - ); - - await GET(request); - - expect(mockProofValidation).toHaveBeenCalledWith(validAddress, validChain); - }); - - it('should return response with empty proofs array when no proofs exist', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'bns_discount', - proofs: [], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${validChain}`, - ); - - const response = await GET(request); - const data = (await response.json()) as SuccessResponse; - - expect(response.status).toBe(200); - expect(data).toEqual(mockResponse); - expect(data.proofs).toEqual([]); - }); - - it('should handle missing address parameter', async () => { - mockProofValidation.mockReturnValue({ - error: 'A single valid address is required', - status: 400, - }); - - const request = new NextRequest(`https://www.base.org/api/proofs/bns?chain=${validChain}`); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'A single valid address is required' }); - }); - - it('should handle missing chain parameter', async () => { - mockProofValidation.mockReturnValue({ - error: 'invalid chain', - status: 400, - }); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}`, - ); - - const response = await GET(request); - const data = (await response.json()) as ErrorResponse; - - expect(response.status).toBe(400); - expect(data).toEqual({ error: 'invalid chain' }); - }); - - it('should pass address directly to getWalletProofs without modification', async () => { - const mixedCaseAddress = '0xAbCdEf1234567890123456789012345678901234'; - const mockResponse: SuccessResponse = { - address: mixedCaseAddress, - namespace: 'bns_discount', - proofs: ['0xproof'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${mixedCaseAddress}&chain=${validChain}`, - ); - - await GET(request); - - expect(mockGetWalletProofs).toHaveBeenCalledWith( - mixedCaseAddress, - base.id, - 'bns_discount', - ); - }); - - it('should call getWalletProofs with BNSDiscount namespace', async () => { - const mockResponse: SuccessResponse = { - address: validAddress, - namespace: 'bns_discount', - proofs: ['0xproof'], - discountValidatorAddress: '0x502df754f25f492cad45ed85a4de0ee7540717e7', - }; - mockGetWalletProofs.mockResolvedValue(mockResponse); - - const request = new NextRequest( - `https://www.base.org/api/proofs/bns?address=${validAddress}&chain=${validChain}`, - ); - - await GET(request); - - expect(mockGetWalletProofs).toHaveBeenCalledWith( - expect.any(String), - expect.any(Number), - 'bns_discount', - ); - }); - }); -}); diff --git a/apps/web/app/(basenames)/api/proofs/bns/route.ts b/apps/web/app/(basenames)/api/proofs/bns/route.ts deleted file mode 100644 index 076948073d8..00000000000 --- a/apps/web/app/(basenames)/api/proofs/bns/route.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { withTimeout } from 'apps/web/app/api/decorators'; -import { logger } from 'apps/web/src/utils/logger'; -import { - getWalletProofs, - ProofsException, - ProofTableNamespace, - proofValidation, -} from 'apps/web/src/utils/proofs'; - -/* -this endpoint returns whether or not the account has a bns account -if result array is empty, user has no bns account -example return: -{ - "address": "0xB18e4C959bccc8EF86D78DC297fb5efA99550d85", - "namespace": "bns_discount", - "proofs": "[0x56ce3bbc909b90035ae373d32c56a9d81d26bb505dd935cdee6afc384bcaed8d, 0x99e940ed9482bf59ba5ceab7df0948798978a1acaee0ecb41f64fe7f40eedd17]" - "discountValidatorAddress": "0x..." -} -*/ -async function handler(req: NextRequest) { - if (req.method !== 'GET') { - return NextResponse.json({ error: 'method not allowed' }, { status: 405 }); - } - const address = req.nextUrl.searchParams.get('address'); - const chain = req.nextUrl.searchParams.get('chain'); - const validationErr = proofValidation(address ?? '', chain ?? ''); - if (validationErr) { - return NextResponse.json({ error: validationErr.error }, { status: validationErr.status }); - } - - try { - const responseData = await getWalletProofs( - address as `0x${string}`, - parseInt(chain as string), - ProofTableNamespace.BNSDiscount, - ); - - return NextResponse.json(responseData); - } catch (error: unknown) { - if (error instanceof ProofsException) { - return NextResponse.json({ error: error.message }, { status: error.statusCode }); - } - logger.error('error getting proofs for bns discount', error); - } - - // If error is not an instance of Error, return a generic error message - return NextResponse.json({ error: 'An unexpected error occurred' }, { status: 500 }); -} - -export const GET = withTimeout(handler); diff --git a/apps/web/app/(basenames)/api/proofs/cbid/route.test.ts b/apps/web/app/(basenames)/api/proofs/cbid/route.test.ts index f1d5fa46657..ac690896612 100644 --- a/apps/web/app/(basenames)/api/proofs/cbid/route.test.ts +++ b/apps/web/app/(basenames)/api/proofs/cbid/route.test.ts @@ -31,8 +31,6 @@ jest.mock('apps/web/src/utils/proofs', () => { getWalletProofs: jest.fn(), ProofTableNamespace: { CBIDDiscount: 'basenames_cbid_discount', - BNSDiscount: 'basenames_bns_discount', - BaseEthHolders: 'basenames_base_eth_holders_discount', }, ProofsException: MockProofsException, }; diff --git a/apps/web/src/addresses/usernames.ts b/apps/web/src/addresses/usernames.ts index d13a21cf6ea..ce716538e73 100644 --- a/apps/web/src/addresses/usernames.ts +++ b/apps/web/src/addresses/usernames.ts @@ -43,26 +43,11 @@ export const USERNAME_CB_DISCOUNT_VALIDATORS: AddressMap = { [base.id]: '0x012076854d030128dc72B34621287Bb585210315', }; -export const USERNAME_BNS_DISCOUNT_VALIDATORS: AddressMap = { - [baseSepolia.id]: '0x1DE649d8b004A44491a7D3ebbb23F4B0DA89DE78', - [base.id]: '0x20b433c640DFb8c2e3C6aBB0533314b2d7B9f2FF', -}; - -export const BASE_DOT_ETH_ERC721_DISCOUNT_VALIDATOR: AddressMap = { - [baseSepolia.id]: '0xa0C7a114E25618538BE7fA7c6552C3122056F775', - [base.id]: '0x55564490a44FDC2aEEa54B60eB1c79F124FD88b9', -}; - export const BUILDATHON_ERC721_DISCOUNT_VALIDATOR: AddressMap = { [baseSepolia.id]: '0x7b5B2dB59c414e15Bf70b59C02E6fb00Ca919dbC', [base.id]: '0xB635802085b405A9C8BA7225ae866f60b63d8503', }; -export const USERNAME_BASE_ETH_HOLDERS_DISCOUNT_VALIDATORS: AddressMap = { - [baseSepolia.id]: '0xA475f24BEa985Ff66c1F0d9D8C23661215418894', - [base.id]: '0x55564490a44FDC2aEEa54B60eB1c79F124FD88b9', -}; - export const USERNAME_1155_DISCOUNT_VALIDATORS: AddressMap = { [baseSepolia.id]: '0xE41Cd25f429E10744938d5048646E721ac630aF3', [base.id]: '0x55246A2AE466257B2fB54d4BB881Fb3f17D8e03e', diff --git a/apps/web/src/components/Basenames/RegistrationContext.test.tsx b/apps/web/src/components/Basenames/RegistrationContext.test.tsx index 8be4db264e9..00253bfc67e 100644 --- a/apps/web/src/components/Basenames/RegistrationContext.test.tsx +++ b/apps/web/src/components/Basenames/RegistrationContext.test.tsx @@ -160,8 +160,6 @@ jest.mock('apps/web/src/utils/usernames', () => ({ COINBASE_VERIFIED_ACCOUNT: 'COINBASE_VERIFIED_ACCOUNT', BASE_BUILDATHON_PARTICIPANT: 'BASE_BUILDATHON_PARTICIPANT', SUMMER_PASS_LVL_3: 'SUMMER_PASS_LVL_3', - BNS_NAME: 'BNS_NAME', - BASE_DOT_ETH_NFT: 'BASE_DOT_ETH_NFT', DISCOUNT_CODE: 'DISCOUNT_CODE', TALENT_PROTOCOL: 'TALENT_PROTOCOL', BASE_WORLD: 'BASE_WORLD', diff --git a/apps/web/src/components/Basenames/RegistrationFaq/index.tsx b/apps/web/src/components/Basenames/RegistrationFaq/index.tsx index ca4ef91fb96..59c785a49b8 100644 --- a/apps/web/src/components/Basenames/RegistrationFaq/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationFaq/index.tsx @@ -150,28 +150,14 @@ export default function RegistrationFAQ() { Buildathon participant NFT -
  • - - base.eth NFT holder - -
  • cb.id username (acquired prior to Fri Aug 9, 2024)
  • -
  • - - BNS name owner - free 4+ letter name (basename.app) - -
  • An equivalent-value discount of 0.001 ETH will be applied if registering a shorter - name, or registering for more than 1 year, with the exception of the BNS name - owner discount (valued at 0.01 ETH per unique address). You will need to pay the - standard registration fees if you wish to keep your Basename after your initial - discount has been fully applied. + name, or registering for more than 1 year. You will need to pay the standard + registration fees if you wish to keep your Basename after your initial discount + has been fully applied.

    diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-nft.svg b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-nft.svg deleted file mode 100644 index 55f870d612a..00000000000 --- a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-nft.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/bns.jpg b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/bns.jpg deleted file mode 100644 index 9cc799e99e3..00000000000 Binary files a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/bns.jpg and /dev/null differ diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.test.tsx b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.test.tsx index aa90df3410d..86829888d2f 100644 --- a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.test.tsx +++ b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.test.tsx @@ -13,8 +13,6 @@ jest.mock('apps/web/src/utils/usernames', () => ({ COINBASE_VERIFIED_ACCOUNT: 'COINBASE_VERIFIED_ACCOUNT', BASE_BUILDATHON_PARTICIPANT: 'BASE_BUILDATHON_PARTICIPANT', SUMMER_PASS_LVL_3: 'SUMMER_PASS_LVL_3', - BNS_NAME: 'BNS_NAME', - BASE_DOT_ETH_NFT: 'BASE_DOT_ETH_NFT', DISCOUNT_CODE: 'DISCOUNT_CODE', TALENT_PROTOCOL: 'TALENT_PROTOCOL', BASE_WORLD: 'BASE_WORLD', @@ -29,8 +27,6 @@ const Discount = { COINBASE_VERIFIED_ACCOUNT: 'COINBASE_VERIFIED_ACCOUNT', BASE_BUILDATHON_PARTICIPANT: 'BASE_BUILDATHON_PARTICIPANT', SUMMER_PASS_LVL_3: 'SUMMER_PASS_LVL_3', - BNS_NAME: 'BNS_NAME', - BASE_DOT_ETH_NFT: 'BASE_DOT_ETH_NFT', DISCOUNT_CODE: 'DISCOUNT_CODE', TALENT_PROTOCOL: 'TALENT_PROTOCOL', BASE_WORLD: 'BASE_WORLD', @@ -134,8 +130,6 @@ jest.mock('next/link', () => { jest.mock('./images/base-buildathon-participant.svg', () => 'base-buildathon-participant.svg'); jest.mock('./images/summer-pass-lvl-3.svg', () => 'summer-pass-lvl-3.svg'); jest.mock('./images/cbid-verification.svg', () => 'cbid-verification.svg'); -jest.mock('./images/bns.jpg', () => 'bns.jpg'); -jest.mock('./images/base-nft.svg', () => 'base-nft.svg'); jest.mock('./images/devcon.png', () => 'devcon.png'); jest.mock('./images/coinbase-one-verification.svg', () => 'coinbase-one-verification.svg'); jest.mock('./images/coinbase-verification.svg', () => 'coinbase-verification.svg'); @@ -275,24 +269,22 @@ describe('RegistrationLearnMoreModal', () => { expect(screen.getByText('A cb.id username')).toBeInTheDocument(); expect(screen.getByText('Base buildathon participant')).toBeInTheDocument(); expect(screen.getByText('Summer Pass Level 3')).toBeInTheDocument(); - expect(screen.getByText('BNS username')).toBeInTheDocument(); - expect(screen.getByText('Base.eth NFT')).toBeInTheDocument(); expect(screen.getByText('Base around the world NFT')).toBeInTheDocument(); expect(screen.getByText('Devcon attendance NFT')).toBeInTheDocument(); }); - it('should render 9 discount icons', () => { + it('should render 7 discount icons', () => { render(); const icons = screen.getAllByTestId('discount-icon'); - expect(icons).toHaveLength(9); + expect(icons).toHaveLength(7); }); - it('should render 9 tooltips for discount items', () => { + it('should render 7 tooltips for discount items', () => { render(); const tooltips = screen.getAllByTestId('tooltip'); - expect(tooltips).toHaveLength(9); + expect(tooltips).toHaveLength(7); }); it('should display correct tooltip content for Coinbase verification', () => { @@ -332,7 +324,7 @@ describe('RegistrationLearnMoreModal', () => { // Some icons should have opacity-40 class (non-qualified ones) const opacityIcons = icons.filter((icon) => icon.className.includes('opacity-40')); // All but one should have opacity (since only CB1 is active) - expect(opacityIcons.length).toBe(8); + expect(opacityIcons.length).toBe(6); }); it('should not apply opacity class to qualified discount items', () => { @@ -350,7 +342,6 @@ describe('RegistrationLearnMoreModal', () => { mockAllActiveDiscounts = new Set([ Discount.COINBASE_VERIFIED_ACCOUNT, Discount.CB1, - Discount.BNS_NAME, ]); }); @@ -358,7 +349,7 @@ describe('RegistrationLearnMoreModal', () => { render(); const qualifiedBadges = screen.getAllByText('Qualified'); - expect(qualifiedBadges).toHaveLength(3); + expect(qualifiedBadges).toHaveLength(2); }); it('should apply opacity to non-qualified discounts when user has multiple discounts', () => { @@ -366,8 +357,8 @@ describe('RegistrationLearnMoreModal', () => { const icons = screen.getAllByTestId('discount-icon'); const opacityIcons = icons.filter((icon) => icon.className.includes('opacity-40')); - // 9 total - 3 active = 6 with opacity - expect(opacityIcons.length).toBe(6); + // 7 total - 2 active = 5 with opacity + expect(opacityIcons.length).toBe(5); }); }); }); diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx index b183c11a6c7..3a02dd33716 100644 --- a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx @@ -8,8 +8,6 @@ import Link from 'next/link'; import baseBuildathonParticipant from './images/base-buildathon-participant.svg'; import summerPassLvl3 from './images/summer-pass-lvl-3.svg'; import cbidVerification from './images/cbid-verification.svg'; -import BNSOwnership from './images/bns.jpg'; -import BaseNFT from './images/base-nft.svg'; import DevconPNG from './images/devcon.png'; import coinbaseOneVerification from './images/coinbase-one-verification.svg'; import coinbaseVerification from './images/coinbase-verification.svg'; @@ -66,20 +64,6 @@ const DISCOUNT_ITEMS: DiscountItem[] = [ tooltipContent: 'Available for anyone holding a Summer Pass Level 3 NFT. Go to wallet.coinbase.com/ocs to get your Summer Pass', }, - { - discount: Discount.BNS_NAME, - icon: BNSOwnership, - alt: 'icon of BNS', - label: 'BNS username', - tooltipContent: 'BNS (.base) username holders are eligible for a 0.01 ETH discount', - }, - { - discount: Discount.BASE_DOT_ETH_NFT, - icon: BaseNFT as StaticImageData, - alt: 'icon of Base', - label: 'Base.eth NFT', - tooltipContent: 'Available for anyone holding a base.eth NFT', - }, { discount: Discount.BASE_WORLD, icon: BaseWorldNFT as StaticImageData, diff --git a/apps/web/src/hooks/useAggregatedDiscountValidators.ts b/apps/web/src/hooks/useAggregatedDiscountValidators.ts index 566010a8281..bbc49438251 100644 --- a/apps/web/src/hooks/useAggregatedDiscountValidators.ts +++ b/apps/web/src/hooks/useAggregatedDiscountValidators.ts @@ -1,7 +1,5 @@ import { AttestationData, - useBNSAttestations, - useBaseDotEthAttestations, useBaseWorldAttestations, useBuildathonAttestations, useCheckCB1Attestations, @@ -25,8 +23,7 @@ export function findFirstValidDiscount( ): DiscountData | undefined { const priorityOrder: Partial> & { default: 3 } = { [Discount.DISCOUNT_CODE]: 0, - [Discount.BNS_NAME]: 1, - [Discount.CB1]: 2, + [Discount.CB1]: 1, default: 3, }; @@ -48,8 +45,6 @@ export function useAggregatedDiscountValidators(code?: string) { useCheckCoinbaseAttestations(); const { data: SummerPassData, loading: loadingSummerPass } = useSummerPassAttestations(); const { data: BuildathonData, loading: loadingBuildathon } = useBuildathonAttestations(); - const { data: BaseDotEthData, loading: loadingBaseDotEth } = useBaseDotEthAttestations(); - const { data: BNSData, loading: loadingBNS } = useBNSAttestations(); const { data: DiscountCodeData, loading: loadingDiscountCode } = useDiscountCodeAttestations(code); const { data: TalentProtocolData, loading: loadingTalentProtocolAttestations } = @@ -64,8 +59,6 @@ export function useAggregatedDiscountValidators(code?: string) { loadingActiveDiscounts || loadingBuildathon || loadingSummerPass || - loadingBaseDotEth || - loadingBNS || loadingDiscountCode || loadingTalentProtocolAttestations || loadingBaseWorld || @@ -109,18 +102,6 @@ export function useAggregatedDiscountValidators(code?: string) { discountKey: validator.key, }; } - if ( - BaseDotEthData && - validator.discountValidator === BaseDotEthData.discountValidatorAddress - ) { - discountMapping[Discount.BASE_DOT_ETH_NFT] = { - ...BaseDotEthData, - discountKey: validator.key, - }; - } - if (BNSData && validator.discountValidator === BNSData.discountValidatorAddress) { - discountMapping[Discount.BNS_NAME] = { ...BNSData, discountKey: validator.key }; - } if ( DiscountCodeData && @@ -165,8 +146,6 @@ export function useAggregatedDiscountValidators(code?: string) { coinbaseData, BuildathonData, SummerPassData, - BaseDotEthData, - BNSData, DiscountCodeData, TalentProtocolData, BaseWorldData, diff --git a/apps/web/src/hooks/useAttestations.ts b/apps/web/src/hooks/useAttestations.ts index 46815670d81..f73e4d6cd19 100644 --- a/apps/web/src/hooks/useAttestations.ts +++ b/apps/web/src/hooks/useAttestations.ts @@ -3,13 +3,11 @@ import { CoinbaseProofResponse } from 'apps/web/app/(basenames)/api/proofs/coinb import { DiscountCodeResponse } from 'apps/web/app/(basenames)/api/proofs/discountCode/route'; import AttestationValidatorABI from 'apps/web/src/abis/AttestationValidator'; import CBIDValidatorABI from 'apps/web/src/abis/CBIdDiscountValidator'; -import EarlyAccessValidatorABI from 'apps/web/src/abis/EarlyAccessValidator'; import ERC1155DiscountValidator from 'apps/web/src/abis/ERC1155DiscountValidator'; import ERC1155DiscountValidatorV2 from 'apps/web/src/abis/ERC1155DiscountValidatorV2'; import ERC721ValidatorABI from 'apps/web/src/abis/ERC721DiscountValidator'; import TalentProtocolDiscountValidatorABI from 'apps/web/src/abis/TalentProtocolDiscountValidator'; import { - BASE_DOT_ETH_ERC721_DISCOUNT_VALIDATOR, BASE_WORLD_DISCOUNT_VALIDATORS, BUILDATHON_ERC721_DISCOUNT_VALIDATOR, DEVCON_DISCOUNT_VALIDATORS, @@ -299,140 +297,6 @@ export function useBuildathonAttestations() { return { data: null, loading: isLoading, error }; } -// mainnet erc721 validator -- uses merkle tree -export function useBaseDotEthAttestations() { - const { address } = useAccount(); - const [APICallLoading, setAPICallLoading] = useState(false); - const { basenameChain } = useBasenameChain(); - const [baseDotEthProofResponse, setBaseDotEthProofResponse] = - useState(null); - const { logError } = useErrors(); - - const discountValidatorAddress = BASE_DOT_ETH_ERC721_DISCOUNT_VALIDATOR[basenameChain.id]; - - useEffect(() => { - async function checkBaseDotEthAttestations(a: string) { - try { - setAPICallLoading(true); - const params = new URLSearchParams(); - params.append('address', a); - params.append('chain', basenameChain.id.toString()); - const response = await fetch(`/api/proofs/baseEthHolders?${params}`); - if (response.ok) { - const result = (await response.json()) as MerkleTreeProofResponse; - setBaseDotEthProofResponse(result); - } - } catch (error) { - logError(error, 'Error checking BaseDotEth attestation'); - } finally { - setAPICallLoading(false); - } - } - - if (address) { - checkBaseDotEthAttestations(address).catch((error) => { - logError(error, 'Error checking BaseDotEth attestation'); - }); - } - }, [address, basenameChain.id, logError]); - - const encodedProof = useMemo( - () => - baseDotEthProofResponse?.proofs - ? encodeAbiParameters([{ type: 'bytes32[]' }], [baseDotEthProofResponse?.proofs]) - : '0x0', - [baseDotEthProofResponse?.proofs], - ); - - const readContractArgs = useMemo(() => { - if (!address) { - return {}; - } - return { - address: discountValidatorAddress, - abi: CBIDValidatorABI, - functionName: 'isValidDiscountRegistration', - args: [address, encodedProof], - }; - }, [address, discountValidatorAddress, encodedProof]); - - const { data: isValid, isLoading, error } = useReadContract(readContractArgs); - - if (isValid && address && baseDotEthProofResponse) { - return { - data: { - discountValidatorAddress: discountValidatorAddress, - discount: Discount.BASE_DOT_ETH_NFT, - validationData: encodedProof, - }, - loading: false, - error: null, - }; - } - return { data: null, loading: APICallLoading || isLoading, error }; -} - -// merkle tree discount calls api endpoint -export function useBNSAttestations() { - const { address } = useAccount(); - const [proofResponse, setProofResponse] = useState(null); - const { basenameChain } = useBasenameChain(); - const { logError } = useErrors(); - - useEffect(() => { - async function checkBNS(a: string) { - const params = new URLSearchParams(); - params.append('address', a); - params.append('chain', basenameChain.id.toString()); - const response = await fetch(`/api/proofs/bns?${params}`); - if (response.ok) { - const result = (await response.json()) as MerkleTreeProofResponse; - setProofResponse(result); - } - } - - if (address) { - checkBNS(address).catch((error) => { - logError(error, 'Error checking BNS discount availability'); - }); - } - }, [address, basenameChain.id, logError]); - - const encodedProof = useMemo( - () => - proofResponse?.proofs - ? encodeAbiParameters([{ type: 'bytes32[]' }], [proofResponse?.proofs]) - : '0x0', - [proofResponse?.proofs], - ); - - const readContractArgs = useMemo(() => { - if (!proofResponse?.proofs || !address) { - return {}; - } - return { - address: proofResponse?.discountValidatorAddress, - abi: EarlyAccessValidatorABI, - functionName: 'isValidDiscountRegistration', - args: [address, encodedProof], - }; - }, [address, proofResponse?.discountValidatorAddress, proofResponse?.proofs, encodedProof]); - - const { data: isValid, isLoading, error } = useReadContract(readContractArgs); - - if (isValid && proofResponse && address) { - return { - data: { - discountValidatorAddress: proofResponse.discountValidatorAddress, - discount: Discount.BNS_NAME, - validationData: encodedProof, - }, - loading: false, - error: null, - }; - } - return { data: null, loading: isLoading, error }; -} // returns info about Discount Codes attestations export function useDiscountCodeAttestations(code?: string) { diff --git a/apps/web/src/utils/proofs/proofs_storage.ts b/apps/web/src/utils/proofs/proofs_storage.ts index 6cd68020efc..fe7918cdffc 100644 --- a/apps/web/src/utils/proofs/proofs_storage.ts +++ b/apps/web/src/utils/proofs/proofs_storage.ts @@ -2,8 +2,6 @@ import { getDb } from 'apps/web/src/utils/datastores/postgres'; import { Address } from 'viem'; export enum ProofTableNamespace { - BNSDiscount = 'basenames_bns_discount', - BaseEthHolders = 'basenames_base_eth_holders_discount', CBIDDiscount = 'basenames_cbid_discount', } diff --git a/apps/web/src/utils/proofs/requests.ts b/apps/web/src/utils/proofs/requests.ts index 4bb93782387..64c5e2db754 100644 --- a/apps/web/src/utils/proofs/requests.ts +++ b/apps/web/src/utils/proofs/requests.ts @@ -6,16 +6,10 @@ import { getProofsByNamespaceAndAddress, ProofTableNamespace, } from 'apps/web/src/utils/proofs/proofs_storage'; -import { - USERNAME_BASE_ETH_HOLDERS_DISCOUNT_VALIDATORS, - USERNAME_BNS_DISCOUNT_VALIDATORS, - USERNAME_CB_ID_DISCOUNT_VALIDATORS, -} from 'apps/web/src/addresses/usernames'; +import { USERNAME_CB_ID_DISCOUNT_VALIDATORS } from 'apps/web/src/addresses/usernames'; const validators: Record> = { [ProofTableNamespace.CBIDDiscount]: USERNAME_CB_ID_DISCOUNT_VALIDATORS, - [ProofTableNamespace.BaseEthHolders]: USERNAME_BASE_ETH_HOLDERS_DISCOUNT_VALIDATORS, - [ProofTableNamespace.BNSDiscount]: USERNAME_BNS_DISCOUNT_VALIDATORS, }; export function proofValidation( diff --git a/apps/web/src/utils/usernames.ts b/apps/web/src/utils/usernames.ts index 3b74ea298e1..a6ca7f62b4c 100644 --- a/apps/web/src/utils/usernames.ts +++ b/apps/web/src/utils/usernames.ts @@ -424,8 +424,6 @@ export enum Discount { COINBASE_VERIFIED_ACCOUNT = 'COINBASE_VERIFIED_ACCOUNT', BASE_BUILDATHON_PARTICIPANT = 'BASE_BUILDATHON_PARTICIPANT', SUMMER_PASS_LVL_3 = 'SUMMER_PASS_LVL_3', - BNS_NAME = 'BNS_NAME', - BASE_DOT_ETH_NFT = 'BASE_DOT_ETH_NFT', DISCOUNT_CODE = 'DISCOUNT_CODE', TALENT_PROTOCOL = 'TALENT_PROTOCOL', BASE_WORLD = 'BASE_WORLD',