Skip to content

Commit

Permalink
feat: Add Swap Settings SwapProvider (#1176)
Browse files Browse the repository at this point in the history
Co-authored-by: Alec Chen <[email protected]>
  • Loading branch information
cpcramer and 0xAlec authored Sep 12, 2024
1 parent 2f7d259 commit 87d128b
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 26 deletions.
12 changes: 12 additions & 0 deletions playground/nextjs-app-router/components/demo/Swap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
SwapAmountInput,
SwapButton,
SwapMessage,
SwapSettings,
SwapSettingsSlippageDescription,
SwapSettingsSlippageInput,
SwapSettingsSlippageTitle,
SwapToggleButton,
} from '@coinbase/onchainkit/swap';
import type { Token } from '@coinbase/onchainkit/token';
Expand Down Expand Up @@ -84,6 +88,14 @@ function SwapComponent() {
) : null}

<Swap className="border" onStatus={handleOnStatus}>
<SwapSettings>
<SwapSettingsSlippageTitle>Max. slippage</SwapSettingsSlippageTitle>
<SwapSettingsSlippageDescription>
Your swap will revert if the prices change by more than the selected
percentage.
</SwapSettingsSlippageDescription>
<SwapSettingsSlippageInput />
</SwapSettings>
<SwapAmountInput
label="Sell"
swappableTokens={swappableTokens}
Expand Down
30 changes: 18 additions & 12 deletions src/swap/components/Swap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SwapAmountInput } from './SwapAmountInput';
import { SwapButton } from './SwapButton';
import { SwapMessage } from './SwapMessage';
import { SwapProvider } from './SwapProvider';
import { SwapSettings } from './SwapSettings';
import { SwapToggleButton } from './SwapToggleButton';

export function Swap({
Expand All @@ -18,23 +19,24 @@ export function Swap({
onSuccess,
title = 'Swap',
}: SwapReact) {
const { inputs, toggleButton, swapButton, swapMessage } = useMemo(() => {
const childrenArray = Children.toArray(children);
return {
inputs: childrenArray.filter(findComponent(SwapAmountInput)),
toggleButton: childrenArray.find(findComponent(SwapToggleButton)),
swapButton: childrenArray.find(findComponent(SwapButton)),
swapMessage: childrenArray.find(findComponent(SwapMessage)),
};
}, [children]);
const { inputs, toggleButton, swapButton, swapMessage, swapSettings } =
useMemo(() => {
const childrenArray = Children.toArray(children);
return {
inputs: childrenArray.filter(findComponent(SwapAmountInput)),
toggleButton: childrenArray.find(findComponent(SwapToggleButton)),
swapButton: childrenArray.find(findComponent(SwapButton)),
swapMessage: childrenArray.find(findComponent(SwapMessage)),
swapSettings: childrenArray.find(findComponent(SwapSettings)),
};
}, [children]);

const isMounted = useIsMounted();

// prevents SSR hydration issue
if (!isMounted) {
return null;
}

return (
<SwapProvider
experimental={experimental}
Expand All @@ -50,10 +52,14 @@ export function Swap({
)}
data-testid="ockSwap_Container"
>
<div className="mb-4">
<h3 className={text.title3} data-testid="ockSwap_Title">
<div className="mb-4 flex items-center justify-between">
<h3
className={cn(text.title3, 'text-inherit')}
data-testid="ockSwap_Title"
>
{title}
</h3>
{swapSettings}
</div>
{inputs[0]}
<div className="relative h-1">{toggleButton}</div>
Expand Down
20 changes: 14 additions & 6 deletions src/swap/components/SwapProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ vi.mock('wagmi', async (importOriginal) => {
};
});

vi.mock('../path/to/maxSlippageModule', () => ({
getMaxSlippage: vi.fn().mockReturnValue(10),
}));

const queryClient = new QueryClient();

const config = createConfig({
Expand Down Expand Up @@ -443,12 +447,16 @@ describe('SwapProvider', () => {
});
const button = screen.getByText('setLifeCycleStatus.success');
fireEvent.click(button);
expect(onStatusMock).toHaveBeenCalledWith({
statusName: 'init',
statusData: {
isMissingRequiredField: false,
maxSlippage: 10,
},
await waitFor(() => {
expect(onStatusMock).toHaveBeenCalledWith(
expect.objectContaining({
statusName: 'init',
statusData: expect.objectContaining({
isMissingRequiredField: false,
maxSlippage: 3,
}),
}),
);
});
});

Expand Down
18 changes: 10 additions & 8 deletions src/swap/components/SwapProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { formatTokenAmount } from '../../internal/utils/formatTokenAmount';
import type { Token } from '../../token';
import { GENERIC_ERROR_MESSAGE } from '../../transaction/constants';
import { isUserRejectedRequestError } from '../../transaction/utils/isUserRejectedRequestError';
import { DEFAULT_MAX_SLIPPAGE } from '../constants';
import { useFromTo } from '../hooks/useFromTo';
import { useResetInputs } from '../hooks/useResetInputs';
import type {
Expand Down Expand Up @@ -46,8 +47,8 @@ export function SwapProvider({
const { address } = useAccount();
// Feature flags
const { useAggregator } = experimental;
const [maxSlippage, _setMaxSlippage] = useState(
experimental.maxSlippage || 3,
const [initialMaxSlippage, _setInitialMaxSlippage] = useState(
experimental.maxSlippage || DEFAULT_MAX_SLIPPAGE,
);
// Core Hooks
const config = useConfig();
Expand All @@ -58,7 +59,7 @@ export function SwapProvider({
statusName: 'init',
statusData: {
isMissingRequiredField: true,
maxSlippage,
maxSlippage: initialMaxSlippage,
},
}); // Component lifecycle
const [hasHandledSuccess, setHasHandledSuccess] = useState(false);
Expand Down Expand Up @@ -117,6 +118,7 @@ export function SwapProvider({
}, [hasHandledSuccess, lifeCycleStatus.statusName, resetInputs]);

useEffect(() => {
const maxSlippage = lifeCycleStatus.statusData.maxSlippage;
// Reset status to init after success has been handled
if (lifeCycleStatus.statusName === 'success' && hasHandledSuccess) {
setLifeCycleStatus({
Expand All @@ -132,7 +134,6 @@ export function SwapProvider({
hasHandledSuccess,
lifeCycleStatus.statusData,
lifeCycleStatus.statusName,
maxSlippage,
]);

const handleToggle = useCallback(() => {
Expand All @@ -150,6 +151,7 @@ export function SwapProvider({
dToken?: Token,
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: TODO Refactor this component
) => {
const maxSlippage = lifeCycleStatus.statusData.maxSlippage;
const source = type === 'from' ? from : to;
const destination = type === 'from' ? to : from;

Expand Down Expand Up @@ -200,8 +202,8 @@ export function SwapProvider({
amount,
amountReference: 'from',
from: source.token,
maxSlippage: String(maxSlippage),
to: destination.token,
maxSlippage: maxSlippage.toString(),
useAggregator,
});
// If request resolves to error response set the quoteError
Expand Down Expand Up @@ -257,13 +259,14 @@ export function SwapProvider({
destination.setLoading(false);
}
},
[from, lifeCycleStatus, maxSlippage, to, useAggregator],
[from, lifeCycleStatus, to, useAggregator],
);

const handleSubmit = useCallback(async () => {
if (!address || !from.token || !to.token || !from.amount) {
return;
}
const maxSlippage = lifeCycleStatus.statusData.maxSlippage;
setLifeCycleStatus({
statusName: 'init',
statusData: {
Expand All @@ -277,9 +280,9 @@ export function SwapProvider({
amount: from.amount,
fromAddress: address,
from: from.token,
maxSlippage: String(maxSlippage),
to: to.token,
useAggregator,
maxSlippage: maxSlippage.toString(),
});
if (isSwapError(response)) {
setLifeCycleStatus({
Expand Down Expand Up @@ -329,7 +332,6 @@ export function SwapProvider({
from.amount,
from.token,
lifeCycleStatus,
maxSlippage,
sendTransactionAsync,
to.token,
useAggregator,
Expand Down
1 change: 1 addition & 0 deletions src/swap/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const DEFAULT_MAX_SLIPPAGE = 3;
export const GENERAL_SWAP_ERROR_CODE = 'SWAP_ERROR';
export const GENERAL_SWAP_QUOTE_ERROR_CODE = 'SWAP_QUOTE_ERROR';
export const GENERAL_SWAP_BALANCE_ERROR_CODE = 'SWAP_BALANCE_ERROR';
Expand Down

0 comments on commit 87d128b

Please sign in to comment.