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

feat: Swap #510

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions site/docs/components/SwapContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ReactElement, useState } from 'react';
import type { Token } from '@coinbase/onchainkit/token';

type SwapContainer = {
children: (
fromAmount: string,
fromToken: Token,
fromTokenBalance: string,
setFromAmount: (a: string) => void,
setFromToken: (t: Token) => void,
setToToken: (t: Token) => void,
toAmount: string,
toToken: Token,
toTokenBalance: string,
) => ReactElement;
};

const TOKEN_BALANCE_MAP: Record<string, string> = {
ETH: '3.5',
USDC: '2.77',
DAI: '4.9',
};

export default function SwapContainer({ children }: SwapContainer) {
const [toToken, setToToken] = useState<Token>();
const [fromToken, setFromToken] = useState<Token>();
const [fromAmount, setFromAmount] = useState('');
const [toAmount, _] = useState('');

const fromTokenBalance = TOKEN_BALANCE_MAP[fromToken?.symbol];
const toTokenBalance = TOKEN_BALANCE_MAP[toToken?.symbol];
return children(
fromAmount,
fromToken,
fromTokenBalance,
setFromAmount,
setFromToken,
setToToken,
toAmount,
toToken,
toTokenBalance,
);
}
121 changes: 121 additions & 0 deletions site/docs/pages/swap/swap.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Swap } from '../../../../src/swap';
import App from '../App';
import SwapContainer from '../../components/SwapContainer.tsx';

# `<Swap />`

The `Swap` component is a comprehensive interface for users to execute token swaps. It includes two instances of the `SwapAmountInput` component, enabling users to specify the amount of tokens to sell and buy. The `SwapTokensButton` component facilitates the swapping of selected tokens with a single click, dynamically interchanging the tokens in the "Sell" and "Buy" fields. Additionally, the component features a "Swap" button for initiating the transaction.

## Usage

:::code-group

```tsx [code]
<Swap
fromAmount={fromAmount}
fromToken={fromToken}
fromTokenBalance={fromTokenBalance}
onSubmit={onSubmit}
setFromAmount={setFromAmount}
setFromToken={setFromToken}
setToToken={setToToken}
swappableTokens={[
{
name: 'Ethereum',
address: '',
symbol: 'ETH',
decimals: 18,
image: 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
chainId: 8453,
},
{
name: 'USDC',
address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
symbol: 'USDC',
decimals: 6,
image:
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2',
chainId: 8453,
},
{
name: 'Dai',
address: '0x50c5725949a6f0c72e6c4a641f24049a917db0cb',
symbol: 'DAI',
decimals: 18,
image:
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/d0/d7/d0d7784975771dbbac9a22c8c0c12928cc6f658cbcf2bbbf7c909f0fa2426dec-NmU4ZWViMDItOTQyYy00Yjk5LTkzODUtNGJlZmJiMTUxOTgy',
chainId: 8453,
},
]}
toAmount={toAmount}
toToken={toToken}
toTokenBalance={toTokenBalance}
/>
```

```html [return html]

```

:::

<App>
<SwapContainer>
{(
fromAmount,
fromToken,
fromTokenBalance,
setFromAmount,
setFromToken,
setToToken,
toAmount,
toToken,
toTokenBalance,
) => (
<Swap
fromAmount={fromAmount}
fromToken={fromToken}
fromTokenBalance={fromTokenBalance}
onSubmit={() => {}}
setFromAmount={setFromAmount}
setFromToken={setFromToken}
setToToken={setToToken}
swappableTokens={[
{
name: 'Ethereum',
address: '',
symbol: 'ETH',
decimals: 18,
image: 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
chainId: 8453,
},
{
name: 'USDC',
address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
symbol: 'USDC',
decimals: 6,
image:
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2',
chainId: 8453,
},
{
name: 'Dai',
address: '0x50c5725949a6f0c72e6c4a641f24049a917db0cb',
symbol: 'DAI',
decimals: 18,
image:
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/d0/d7/d0d7784975771dbbac9a22c8c0c12928cc6f658cbcf2bbbf7c909f0fa2426dec-NmU4ZWViMDItOTQyYy00Yjk5LTkzODUtNGJlZmJiMTUxOTgy',
chainId: 8453,
},
]}
toAmount={toAmount}
toToken={toToken}
toTokenBalance={toTokenBalance}
/>
)}
</SwapContainer>
</App>

## Props

[`SwapReact`](/swap/types#SwapReact)
18 changes: 18 additions & 0 deletions site/docs/pages/swap/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,21 @@ type SwapAmountInputReact = {
tokenBalance?: string; // Amount of selected token user owns
};
```

## `SwapReact`

```ts
export type SwapReact = {
fromAmount?: string; // The amount of the token to be sold
fromToken?: Token; // The token that the user is selling
fromTokenBalance?: string; // The balance of the token that the user is selling
onSubmit: () => void; // A callback function to submit the swap transaction
setFromAmount: (amount: string) => void; // A callback function to set the amount of the token to be sold
setFromToken: (token: Token) => void; // A callback function to set the token to be sold
setToToken: (token: Token) => void; // A callback function to set the token to be bought
swappableTokens: Token[]; // An array of tokens available for swapping
toAmount?: string; // The amount of the token to be bought
toToken?: Token; // The token that the user is buying
toTokenBalance?: string; // The balance of the token that the user is buying
};
```
4 changes: 4 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ export const sidebar = [
{
text: 'Components',
items: [
{
text: 'Swap',
link: '/swap/swap',
},
{
text: 'SwapAmountInput',
link: '/swap/swap-amount-input',
Expand Down
99 changes: 99 additions & 0 deletions src/swap/components/Swap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useCallback } from 'react';
import { SwapAmountInput } from './SwapAmountInput';
import { SwapReact, SwapTokensButtonReact } from '../types';

const swapIcon = (
<svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-testid="SwapIcon"
>
<g clip-path="url(#clip0_2077_4627)">
<path
d="M14.5659 4.93434L13.4345 6.06571L11.8002 4.43139L11.8002 10.75L10.2002 10.75L10.2002 4.43139L8.56592 6.06571L7.43455 4.93434L11.0002 1.36865L14.5659 4.93434ZM8.56592 12.0657L5.00023 15.6314L1.43455 12.0657L2.56592 10.9343L4.20023 12.5687L4.20023 6.25002L5.80023 6.25002L5.80023 12.5687L7.43455 10.9343L8.56592 12.0657Z"
fill="#0A0B0D"
/>
</g>
<defs>
<clipPath id="clip0_2077_4627">
<rect width="16" height="16" fill="white" transform="translate(0 0.5)" />
</clipPath>
</defs>
</svg>
);

export function SwapTokensButton({ onClick }: SwapTokensButtonReact) {
return (
<div
className="absolute left-2/4 top-2/4 flex h-12 w-12 -translate-x-2/4 -translate-y-2/4 cursor-pointer items-center justify-center rounded-[50%] border border-solid border-gray-100 bg-white"
onClick={onClick}
data-testid="SwapTokensButton"
>
{swapIcon}
</div>
);
}

export function Swap({
fromAmount,
fromToken,
fromTokenBalance,
onSubmit,
setFromAmount,
setFromToken,
setToToken,
swappableTokens,
toAmount,
toToken,
toTokenBalance,
}: SwapReact) {
const handleSwapTokensClick = useCallback(() => {
if (!toToken || !fromToken) {
return;
}
const prevFromToken = fromToken;
setFromToken(toToken);
setToToken(prevFromToken);
}, [fromToken, toToken, setFromToken, setToToken]);

return (
<div className="flex w-[400px] flex-col rounded-xl bg-white">
<label className="box-border w-full border-b border-solid p-4 text-base font-semibold leading-6 text-[#030712] shadow-[0px_4px_4px_0px_rgba(3,7,18,0.05)]">
Swap
</label>
<div className="relative flex flex-col">
<SwapAmountInput
amount={fromAmount}
label="Sell"
setAmount={setFromAmount}
setToken={setFromToken}
token={fromToken}
tokenBalance={fromTokenBalance}
swappableTokens={swappableTokens}
/>
<SwapTokensButton onClick={handleSwapTokensClick} />
<SwapAmountInput
amount={toAmount}
disabled
displayMaxButton={false}
label="Buy"
setToken={setToToken}
swappableTokens={swappableTokens}
token={toToken}
tokenBalance={toTokenBalance}
/>
</div>
<div className="w-full p-4">
<button
className="w-full rounded-[100px] bg-blue-700 px-4 py-3 text-base font-medium leading-6 text-white"
onClick={onSubmit}
>
Swap
</button>
</div>
</div>
);
}
1 change: 1 addition & 0 deletions src/swap/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// 🌲☀️🌲
export { getQuote } from './core/getQuote';
export { Swap } from './components/Swap'
export type {
Fee,
GetQuoteParams,
Expand Down
24 changes: 22 additions & 2 deletions src/swap/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,30 @@ export type Transaction = {
export type SwapAmountInputReact = {
amount?: string; // Token amount
disabled?: boolean; // Whether the input is disabled
displayMaxButton?: boolean; // Whether the max button is displayed
label: string; // Descriptive label for the input field
setAmount: (amount: string) => void; // Callback function when the amount changes
setToken: () => void; // Callback function when the token selector is clicked
setAmount?: (amount: string) => void; // Callback function when the amount changes
setToken: (token: Token) => void; // Callback function when the token selector is clicked
swappableTokens: Token[]; // Tokens available for swap
token?: Token; // Selected token
tokenBalance?: string; // Amount of selected token user owns
};

export type SwapTokensButtonReact = {
onClick: () => void;
};

export type SwapReact = {
fromAmount?: string; // The amount of the token to be sold
fromToken?: Token; // The token that the user is selling
fromTokenBalance?: string; // The balance of the token that the user is selling
// TODO: add argument to this function
onSubmit: () => void; // A callback function to submit the swap transaction
setFromAmount: (amount: string) => void; // A callback function to set the amount of the token to be sold
setFromToken: (token: Token) => void; // A callback function to set the token to be sold
setToToken: (token: Token) => void; // A callback function to set the token to be bought
swappableTokens: Token[]; // An array of tokens available for swapping
toAmount?: string; // The amount of the token to be bought
toToken?: Token; // The token that the user is buying
toTokenBalance?: string; // The balance of the token that the user is buying
};
Loading