Skip to content

Commit

Permalink
feat: send utils (#1875)
Browse files Browse the repository at this point in the history
  • Loading branch information
brendan-defi authored Jan 24, 2025
1 parent 4501399 commit 92f2364
Show file tree
Hide file tree
Showing 13 changed files with 623 additions and 4 deletions.
10 changes: 9 additions & 1 deletion src/identity/utils/getAddress.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { publicClient } from '@/core/network/client';
import { getChainPublicClient } from '@/core/network/getChainPublicClient';
import { base, baseSepolia, mainnet } from 'viem/chains';
import { base, baseSepolia, mainnet, optimism } from 'viem/chains';
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import { getAddress } from './getAddress';

Expand Down Expand Up @@ -55,4 +55,12 @@ describe('getAddress', () => {
expect(mockGetEnsAddress).toHaveBeenCalledWith({ name });
expect(getChainPublicClient).toHaveBeenCalledWith(mainnet);
});

it('should throw an error on unsupported chain', async () => {
await expect(
getAddress({ name: 'test.ens', chain: optimism }),
).rejects.toBe(
'ChainId not supported, name resolution is only supported on Ethereum and Base.',
);
});
});
17 changes: 17 additions & 0 deletions src/identity/utils/getAddress.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { getChainPublicClient } from '@/core/network/getChainPublicClient';
import { isBase } from '@/core/utils/isBase';
import { isEthereum } from '@/core/utils/isEthereum';
import { RESOLVER_ADDRESSES_BY_CHAIN_ID } from '@/identity/constants';
import type { GetAddress, GetAddressReturnType } from '@/identity/types';
import { isBasename } from '@/identity/utils/isBasename';
import { mainnet } from 'viem/chains';

/**
Expand All @@ -9,10 +13,23 @@ export const getAddress = async ({
name,
chain = mainnet,
}: GetAddress): Promise<GetAddressReturnType> => {
const chainIsBase = isBase({ chainId: chain.id });
const chainIsEthereum = isEthereum({ chainId: chain.id });
const chainSupportsUniversalResolver = chainIsEthereum || chainIsBase;

if (!chainSupportsUniversalResolver) {
return Promise.reject(
'ChainId not supported, name resolution is only supported on Ethereum and Base.',
);
}

const client = getChainPublicClient(chain);
// Gets address for ENS name.
const address = await client.getEnsAddress({
name,
universalResolverAddress: isBasename(name)
? RESOLVER_ADDRESSES_BY_CHAIN_ID[chain.id]
: undefined,
});
return address ?? null;
};
4 changes: 1 addition & 3 deletions src/identity/utils/getName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const getName = async ({
);
}

let client = getChainPublicClient(chain);
const client = getChainPublicClient(chain);

if (chainIsBase) {
const addressReverseNode = convertReverseNodeToBytes(address, base.id);
Expand All @@ -45,8 +45,6 @@ export const getName = async ({
}
}

// Default to mainnet
client = getChainPublicClient(mainnet);
// ENS username
const ensName = await client.getEnsName({
address,
Expand Down
30 changes: 30 additions & 0 deletions src/identity/utils/isEns.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { isEns } from './isEns';

describe('isEns', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('Returns true for mainnet names', async () => {
expect(isEns('shrek.eth')).toBe(true);
expect(isEns('shrek.optimisim.eth')).toBe(true);
expect(isEns('shrek.baaaaaes.eth')).toBe(true);
});

it('Returns true for mainnet sepolia names', async () => {
expect(isEns('shrek.test.eth')).toBe(true);
});

it('Returns false for basenames', async () => {
expect(isEns('shrek.base.eth')).toBe(false);
expect(isEns('shrek.basetest.eth')).toBe(false);
});

it('Returns false for any other name', async () => {
expect(isEns('shrek.optimisim')).toBe(false);
expect(isEns('shrek.baaaaaes')).toBe(false);
expect(isEns('shrek')).toBe(false);
expect(isEns('shrek.sol')).toBe(false);
});
});
9 changes: 9 additions & 0 deletions src/identity/utils/isEns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const isEns = (username: string) => {
if (username.endsWith('.base.eth') || username.endsWith('.basetest.eth')) {
return false;
}
if (username.endsWith('.eth') || username.endsWith('.test.eth')) {
return true;
}
return false;
};
104 changes: 104 additions & 0 deletions src/internal/hooks/useAmountInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useAmountInput } from './useAmountInput';

describe('useAmountInput', () => {
const defaultProps = {
setFiatAmount: vi.fn(),
setCryptoAmount: vi.fn(),
selectedInputType: 'fiat' as const,
exchangeRate: '2',
};

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

describe('handleFiatChange', () => {
it('should handle fiat input and calculate crypto amount', () => {
const { result } = renderHook(() => useAmountInput(defaultProps));

act(() => {
result.current.handleFiatChange('100.456');
});

expect(defaultProps.setFiatAmount).toHaveBeenCalledWith('100.46');
expect(defaultProps.setCryptoAmount).toHaveBeenCalledWith('200.92');
});

it('should set empty crypto amount when fiat is zero', () => {
const { result } = renderHook(() => useAmountInput(defaultProps));

act(() => {
result.current.handleFiatChange('0');
});

expect(defaultProps.setCryptoAmount).toHaveBeenCalledWith('');
});
});

describe('handleCryptoChange', () => {
it('should handle crypto input and calculate fiat amount', () => {
const { result } = renderHook(() => useAmountInput(defaultProps));

act(() => {
result.current.handleCryptoChange('200.12345678');
});

expect(defaultProps.setCryptoAmount).toHaveBeenCalledWith('200.12345678');
expect(defaultProps.setFiatAmount).toHaveBeenCalledWith('100.06');
});

it('should set empty fiat amount when crypto calculation is zero', () => {
const { result } = renderHook(() => useAmountInput(defaultProps));

act(() => {
result.current.handleCryptoChange('0');
});

expect(defaultProps.setFiatAmount).toHaveBeenCalledWith('');
});
});

describe('handleChange', () => {
it('should call handleFiatChange when selectedInputType is fiat', () => {
const { result } = renderHook(() => useAmountInput(defaultProps));
const onChange = vi.fn();

act(() => {
result.current.handleChange('100', onChange);
});

expect(defaultProps.setFiatAmount).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith('100');
});

it('should call handleCryptoChange when selectedInputType is crypto', () => {
const { result } = renderHook(() =>
useAmountInput({
...defaultProps,
selectedInputType: 'crypto',
}),
);
const onChange = vi.fn();

act(() => {
result.current.handleChange('100', onChange);
});

expect(defaultProps.setCryptoAmount).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith('100');
});

it('should work without optional onChange callback', () => {
const { result } = renderHook(() => useAmountInput(defaultProps));

act(() => {
result.current.handleChange('100');
});

expect(defaultProps.setFiatAmount).toHaveBeenCalled();
});
});
});
67 changes: 67 additions & 0 deletions src/internal/hooks/useAmountInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { truncateDecimalPlaces } from '@/internal/utils/truncateDecimalPlaces';
import { useCallback, useMemo } from 'react';

type UseAmountInputParams = {
setFiatAmount: (value: string) => void;
setCryptoAmount: (value: string) => void;
selectedInputType: 'fiat' | 'crypto';
exchangeRate: string;
};

export const useAmountInput = ({
setFiatAmount,
setCryptoAmount,
selectedInputType,
exchangeRate,
}: UseAmountInputParams) => {
const handleFiatChange = useCallback(
(value: string) => {
const fiatValue = truncateDecimalPlaces(value, 2);
setFiatAmount(fiatValue);

const calculatedCryptoValue = String(
Number(fiatValue) * Number(exchangeRate),
);
const resultCryptoValue = truncateDecimalPlaces(calculatedCryptoValue, 8);
setCryptoAmount(calculatedCryptoValue === '0' ? '' : resultCryptoValue);
},
[exchangeRate, setFiatAmount, setCryptoAmount],
);

const handleCryptoChange = useCallback(
(value: string) => {
const truncatedValue = truncateDecimalPlaces(value, 8);
setCryptoAmount(truncatedValue);

const calculatedFiatValue = String(
Number(truncatedValue) / Number(exchangeRate),
);

const resultFiatValue = truncateDecimalPlaces(calculatedFiatValue, 2);
setFiatAmount(resultFiatValue === '0' ? '' : resultFiatValue);
},
[exchangeRate, setFiatAmount, setCryptoAmount],
);

const handleChange = useCallback(
(value: string, onChange?: (value: string) => void) => {
if (selectedInputType === 'fiat') {
handleFiatChange(value);
} else {
handleCryptoChange(value);
}

onChange?.(value);
},
[handleFiatChange, handleCryptoChange, selectedInputType],
);

return useMemo(
() => ({
handleChange,
handleFiatChange,
handleCryptoChange,
}),
[handleChange, handleFiatChange, handleCryptoChange],
);
};
Loading

0 comments on commit 92f2364

Please sign in to comment.