diff --git a/playground/nextjs-app-router/components/AppProvider.tsx b/playground/nextjs-app-router/components/AppProvider.tsx index f8add83c5..b67e7f967 100644 --- a/playground/nextjs-app-router/components/AppProvider.tsx +++ b/playground/nextjs-app-router/components/AppProvider.tsx @@ -8,6 +8,7 @@ import { WalletPreference } from './form/wallet-type'; export enum OnchainKitComponent { Identity = 'identity', Swap = 'swap', + SwapDefault = 'swap-default', Transaction = 'transaction', Wallet = 'wallet', } diff --git a/playground/nextjs-app-router/components/Demo.tsx b/playground/nextjs-app-router/components/Demo.tsx index ce6b68e77..1ddb5aa39 100644 --- a/playground/nextjs-app-router/components/Demo.tsx +++ b/playground/nextjs-app-router/components/Demo.tsx @@ -7,6 +7,7 @@ import { WalletType } from '@/components/form/wallet-type'; import { useContext, useEffect, useState } from 'react'; import IdentityDemo from './demo/Identity'; import SwapDemo from './demo/Swap'; +import SwapDefaultDemo from './demo/SwapDefault'; import TransactionDemo from './demo/Transaction'; import WalletDemo from './demo/Wallet'; import { ActiveComponent } from './form/active-component'; @@ -56,6 +57,10 @@ function Demo() { return ; } + if (activeComponent === OnchainKitComponent.SwapDefault) { + return ; + } + return <>; } diff --git a/playground/nextjs-app-router/components/demo/SwapDefault.tsx b/playground/nextjs-app-router/components/demo/SwapDefault.tsx new file mode 100644 index 000000000..30e1f0d64 --- /dev/null +++ b/playground/nextjs-app-router/components/demo/SwapDefault.tsx @@ -0,0 +1,118 @@ +import { ENVIRONMENT, ENVIRONMENT_VARIABLES } from '@/lib/constants'; +import { type LifecycleStatus, SwapDefault } from '@coinbase/onchainkit/swap'; +import type { SwapError } from '@coinbase/onchainkit/swap'; +import type { Token } from '@coinbase/onchainkit/token'; +import { useCallback, useContext } from 'react'; +import type { TransactionReceipt } from 'viem'; +import { base } from 'viem/chains'; +import { AppContext } from '../AppProvider'; + +const DEFAULT_MAX_SLIPPAGE = 3; + +function SwapDefaultComponent() { + const { chainId, defaultMaxSlippage, paymasters } = useContext(AppContext); + + const degenToken: Token = { + name: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + symbol: 'DEGEN', + decimals: 18, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm', + chainId: base.id, + }; + + const ethToken: Token = { + name: 'ETH', + address: '', + symbol: 'ETH', + decimals: 18, + image: + 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png', + chainId: base.id, + }; + + const usdcToken: Token = { + name: 'USDC', + address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + symbol: 'USDC', + decimals: 6, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2', + chainId: base.id, + }; + + const wethToken: Token = { + name: 'Wrapped Ether', + address: '0x4200000000000000000000000000000000000006', + symbol: 'WETH', + decimals: 6, + image: + 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/47/bc/47bc3593c2dec7c846b66b7ba5f6fa6bd69ec34f8ebb931f2a43072e5aaac7a8-YmUwNmRjZDUtMjczYy00NDFiLWJhZDUtMzgwNjFmYWM0Njkx', + chainId: base.id, + }; + + const to = [ethToken, degenToken, usdcToken, wethToken]; + const from = [usdcToken, degenToken, ethToken, wethToken]; + + const handleOnStatus = useCallback((lifecycleStatus: LifecycleStatus) => { + console.log('Status:', lifecycleStatus); + }, []); + + const handleOnSuccess = useCallback( + (transactionReceipt: TransactionReceipt) => { + console.log('Success:', transactionReceipt); + }, + [], + ); + + const handleOnError = useCallback((swapError: SwapError) => { + console.log('Error:', swapError); + }, []); + + return ( +
+ {chainId !== base.id ? ( +
+
+ Swap Demo is only available on Base. +
+ You're connected to a different network. Switch to Base to continue + using the app. +
+
+ ) : ( + <> + )} + + {ENVIRONMENT_VARIABLES[ENVIRONMENT.ENVIRONMENT] === 'production' && + chainId === base.id ? ( +
+ Note: Swap is disabled on production. To test, run the app locally. +
+ ) : null} + + +
+ ); +} + +export default function SwapDefaultDemo() { + return ( +
+ +
+ ); +} diff --git a/playground/nextjs-app-router/components/form/active-component.tsx b/playground/nextjs-app-router/components/form/active-component.tsx index 00e4da4f5..fbcdb9af6 100644 --- a/playground/nextjs-app-router/components/form/active-component.tsx +++ b/playground/nextjs-app-router/components/form/active-component.tsx @@ -30,6 +30,9 @@ export function ActiveComponent() { Transaction Swap + + SwapDefault + Wallet diff --git a/src/swap/components/SwapButton.tsx b/src/swap/components/SwapButton.tsx index 24ab8d8da..d616ab561 100644 --- a/src/swap/components/SwapButton.tsx +++ b/src/swap/components/SwapButton.tsx @@ -27,6 +27,9 @@ export function SwapButton({ className, disabled = false }: SwapButtonReact) { disabled || isLoading; + // disable swap if to and from token are the same + const isSwapInvalid = to.token?.address === from.token?.address; + // prompt user to connect wallet if (!isDisabled && !address) { return ; @@ -44,7 +47,7 @@ export function SwapButton({ className, disabled = false }: SwapButtonReact) { className, )} onClick={() => handleSubmit()} - disabled={isDisabled} + disabled={isDisabled || isSwapInvalid} data-testid="ockSwapButton_Button" > {isLoading ? ( diff --git a/src/swap/components/SwapDefault.tsx b/src/swap/components/SwapDefault.tsx new file mode 100644 index 000000000..f788845e6 --- /dev/null +++ b/src/swap/components/SwapDefault.tsx @@ -0,0 +1,63 @@ +import { SwapDefaultReact } from '../types'; +import { Swap } from './Swap'; +import { SwapAmountInput } from './SwapAmountInput'; +import { SwapButton } from './SwapButton'; +import { SwapMessage } from './SwapMessage'; +import { SwapSettings } from './SwapSettings'; +import { SwapSettingsSlippageDescription } from './SwapSettingsSlippageDescription'; +import { SwapSettingsSlippageInput } from './SwapSettingsSlippageInput'; +import { SwapSettingsSlippageTitle } from './SwapSettingsSlippageTitle'; +import { SwapToast } from './SwapToast'; +import { SwapToggleButton } from './SwapToggleButton'; + +export function SwapDefault({ + config, + className, + disabled, + experimental, + from, + isSponsored = false, + onError, + onStatus, + onSuccess, + title = 'Swap', + to, +}: SwapDefaultReact) { + return ( + + + Max. slippage + + Your swap will revert if the prices change by more than the selected + percentage. + + + + + + + + + + + ); +} diff --git a/src/swap/index.ts b/src/swap/index.ts index e65691192..56f645247 100644 --- a/src/swap/index.ts +++ b/src/swap/index.ts @@ -2,6 +2,7 @@ export { Swap } from './components/Swap'; export { SwapAmountInput } from './components/SwapAmountInput'; export { SwapButton } from './components/SwapButton'; +export { SwapDefault } from './components/SwapDefault'; export { SwapMessage } from './components/SwapMessage'; export { SwapSettings } from './components/SwapSettings'; export { SwapSettingsSlippageDescription } from './components/SwapSettingsSlippageDescription'; diff --git a/src/swap/types.ts b/src/swap/types.ts index 9124efb48..bd179238c 100644 --- a/src/swap/types.ts +++ b/src/swap/types.ts @@ -296,6 +296,15 @@ export type SwapReact = { title?: string; // Title for the Swap component. (default: "Swap") }; +/** + * Note: exported as public Type + */ +export type SwapDefaultReact = { + to: Token[]; // Swappable tokens + from: Token[]; // Swappable tokens + disabled?: boolean; // Disables swap button +} & Omit; + /** * Note: exported as public Type */