diff --git a/package-lock.json b/package-lock.json
index 4d0e4e3f..98f05b2b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,13 +10,13 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
- "@cityofzion/blockchain-service": "1.8.0",
+ "@cityofzion/blockchain-service": "1.10.0",
"@cityofzion/bs-asteroid-sdk": "0.9.0",
- "@cityofzion/bs-electron": "0.1.19",
- "@cityofzion/bs-ethereum": "2.7.0",
- "@cityofzion/bs-neo-legacy": "1.6.0",
- "@cityofzion/bs-neo3": "1.8.2",
- "@cityofzion/bs-swap": "0.1.1",
+ "@cityofzion/bs-electron": "0.1.21",
+ "@cityofzion/bs-ethereum": "2.7.2",
+ "@cityofzion/bs-neo-legacy": "1.6.2",
+ "@cityofzion/bs-neo3": "1.8.3",
+ "@cityofzion/bs-swap": "0.2.1",
"@cityofzion/neon-core": "5.5.1",
"@cityofzion/neon-js": "5.5.1",
"@cityofzion/wallet-connect-sdk-wallet-core": "4.4.0",
@@ -568,9 +568,9 @@
}
},
"node_modules/@cityofzion/blockchain-service": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@cityofzion/blockchain-service/-/blockchain-service-1.8.0.tgz",
- "integrity": "sha512-g4FeVLGMxKirbI16q6KpAQ3gsZJG6uzGEOkTEU7Xqahic+wDtp9EOYMt47327g8WiNFr9uw6L6BRLgTFglz0TQ==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@cityofzion/blockchain-service/-/blockchain-service-1.10.0.tgz",
+ "integrity": "sha512-VaHlwHFSawK3TilN0wgv8tEOvBLuaKxFl7fNIGacnnpj4GwWyc+0xzv0ZwwzwgDgwneyM153roKVkcuzH/k+fg==",
"license": "MIT",
"dependencies": {
"@ledgerhq/hw-transport": "~6.30.5",
@@ -604,12 +604,12 @@
}
},
"node_modules/@cityofzion/bs-electron": {
- "version": "0.1.19",
- "resolved": "https://registry.npmjs.org/@cityofzion/bs-electron/-/bs-electron-0.1.19.tgz",
- "integrity": "sha512-+jNjCiSCgUstShrwyan8nqaipeZDtKMEn3Qz6Eq7a3Ebxgq2bsyMGqcG+ntvrsjU0Voa/AonaLCNrdL9f9lyWw==",
+ "version": "0.1.21",
+ "resolved": "https://registry.npmjs.org/@cityofzion/bs-electron/-/bs-electron-0.1.21.tgz",
+ "integrity": "sha512-Um+zCAvxPcZrQ8bm41YaWBX8K32wc9TBHDChOIZLlOfGj5yV+d7QB2v99iST47TkmjGLGTSTFLFQUewYdNQZlg==",
"license": "MIT",
"dependencies": {
- "@cityofzion/blockchain-service": "1.8.0",
+ "@cityofzion/blockchain-service": "1.10.0",
"lodash.clonedeep": "~4.5.0"
},
"peerDependencies": {
@@ -618,12 +618,12 @@
}
},
"node_modules/@cityofzion/bs-ethereum": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/@cityofzion/bs-ethereum/-/bs-ethereum-2.7.0.tgz",
- "integrity": "sha512-tl63b6IvlsBLtd/1KTmoP+XjFLbWWfy9rzeDRYcq+jAoOvjIPEZ1LyAAr8AD2pMax24ZED5YXhTBGEM7UXSdVg==",
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/@cityofzion/bs-ethereum/-/bs-ethereum-2.7.2.tgz",
+ "integrity": "sha512-5kQJ4GWaPZ57FW72Zw5/l5LtqKxUjddANvydNfl65HyZtdHrT0hM5s5XAbCwDBjvjT5XCt+47tQzlhuTShF4EQ==",
"license": "MIT",
"dependencies": {
- "@cityofzion/blockchain-service": "1.8.0",
+ "@cityofzion/blockchain-service": "1.10.0",
"@ethersproject/abstract-signer": "~5.7.0",
"@ethersproject/bignumber": "5.7.0",
"@ethersproject/bytes": "5.7.0",
@@ -757,12 +757,12 @@
}
},
"node_modules/@cityofzion/bs-neo-legacy": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@cityofzion/bs-neo-legacy/-/bs-neo-legacy-1.6.0.tgz",
- "integrity": "sha512-oJs9c6BUekhtTDxLcUo/4f3oCvRA8L5snxKfL0fq9dJDjvm731rdSPI6abq0d2FCfsuve/s47D25SLwcppUDvg==",
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@cityofzion/bs-neo-legacy/-/bs-neo-legacy-1.6.2.tgz",
+ "integrity": "sha512-GnwxdSqvJ5NoyPbL9QdVx1kUqQpivSByOjoRIEeMxR60hJ07f78mOThbLey80fQyfR3TZiMzpk8BQr4YRIFZ1Q==",
"license": "MIT",
"dependencies": {
- "@cityofzion/blockchain-service": "1.8.0",
+ "@cityofzion/blockchain-service": "1.10.0",
"@cityofzion/bs-asteroid-sdk": "0.9.0",
"@cityofzion/dora-ts": "0.0.11",
"@cityofzion/neon-js": "4.8.3"
@@ -850,11 +850,12 @@
}
},
"node_modules/@cityofzion/bs-neo3": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/@cityofzion/bs-neo3/-/bs-neo3-1.8.2.tgz",
- "integrity": "sha512-Q5I7ogjoo6YEyniC43EWaOCT91gHX0vZx4cTQ+UnDBvOr5oiZThBSK7nkFT428CW6hZR20YCuMQdWvFkfnpvoA==",
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/@cityofzion/bs-neo3/-/bs-neo3-1.8.3.tgz",
+ "integrity": "sha512-8fHDxIv4wTdB7m7HKqu5xVb4Rw8rxV+5AYMcS6SsMyZTdMQGnqxW2QfvZ17wLuCdbScu4HJIIPKurDUujkl5xQ==",
+ "license": "MIT",
"dependencies": {
- "@cityofzion/blockchain-service": "1.9.0",
+ "@cityofzion/blockchain-service": "1.10.0",
"@cityofzion/bs-asteroid-sdk": "0.9.0",
"@cityofzion/dora-ts": "0.0.11",
"@cityofzion/neon-core": "5.5.1",
@@ -868,17 +869,10 @@
"query-string": "7.1.3"
}
},
- "node_modules/@cityofzion/bs-neo3/node_modules/@cityofzion/blockchain-service": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@cityofzion/blockchain-service/-/blockchain-service-1.9.0.tgz",
- "integrity": "sha512-lolAD/TzXyIZxifYI/mFBvZSeBIblWfotfdpKjpfarVCZFQmNnOY/ebeX+5o+Twq+W1H+YebjxtYMt+zlvCUiw==",
- "dependencies": {
- "@ledgerhq/hw-transport": "~6.30.5",
- "axios": "1.5.1"
- }
- },
"node_modules/@cityofzion/bs-neo3/node_modules/@ledgerhq/hw-transport": {
"version": "6.30.6",
+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.30.6.tgz",
+ "integrity": "sha512-fT0Z4IywiuJuZrZE/+W0blkV5UCotDPFTYKLkKCLzYzuE6javva7D/ajRaIeR+hZ4kTmKF4EqnsmDCXwElez+w==",
"license": "Apache-2.0",
"dependencies": {
"@ledgerhq/devices": "^8.3.0",
@@ -889,6 +883,8 @@
},
"node_modules/@cityofzion/bs-neo3/node_modules/axios": {
"version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
+ "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.0",
@@ -904,47 +900,17 @@
}
},
"node_modules/@cityofzion/bs-swap": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@cityofzion/bs-swap/-/bs-swap-0.1.1.tgz",
- "integrity": "sha512-kIJcOMQDTicj7FJWL5n6o5E1L12zP/QBouTWu2XNOGjNtWEGIBpckqzYChzsZwrBjpuZD4NhGeNgXaMj3ZVlMg==",
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@cityofzion/bs-swap/-/bs-swap-0.2.1.tgz",
+ "integrity": "sha512-LB3pHjYHyb8Gruhux8zi5AfKaYrATIDL6bPKu72Z9QS6Zs5NaOnRDN5UzugT6rrutWJloEelI5Vw8zUMZ1s3WA==",
"license": "MIT",
"dependencies": {
- "@cityofzion/blockchain-service": "1.8.0",
- "@cityofzion/bs-neo3": "1.8.0",
+ "@cityofzion/blockchain-service": "1.10.0",
+ "@cityofzion/bs-neo3": "1.8.3",
"axios": "1.5.1",
"lodash": "~4.17.21"
}
},
- "node_modules/@cityofzion/bs-swap/node_modules/@cityofzion/bs-neo3": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@cityofzion/bs-neo3/-/bs-neo3-1.8.0.tgz",
- "integrity": "sha512-YW++gN2qvl6ZhWf82JFkCHazNsVLJmskS3rIP38rFjgZqVNDrVI7jCMvMeT9k+Qwh9pAKBZHGjAYNrpk8cNqqw==",
- "dependencies": {
- "@cityofzion/blockchain-service": "1.8.0",
- "@cityofzion/bs-asteroid-sdk": "0.9.0",
- "@cityofzion/dora-ts": "0.0.11",
- "@cityofzion/neon-core": "5.5.1",
- "@cityofzion/neon-dappkit": "0.4.1",
- "@cityofzion/neon-js": "5.5.1",
- "@ledgerhq/hw-transport": "~6.30.5",
- "axios": "1.5.1",
- "bignumber.js": "^9.1.2",
- "isomorphic-ws": "^5.0.0",
- "lodash.clonedeep": "^4.5.0",
- "query-string": "7.1.3"
- }
- },
- "node_modules/@cityofzion/bs-swap/node_modules/@ledgerhq/hw-transport": {
- "version": "6.30.6",
- "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.30.6.tgz",
- "integrity": "sha512-fT0Z4IywiuJuZrZE/+W0blkV5UCotDPFTYKLkKCLzYzuE6javva7D/ajRaIeR+hZ4kTmKF4EqnsmDCXwElez+w==",
- "dependencies": {
- "@ledgerhq/devices": "^8.3.0",
- "@ledgerhq/errors": "^6.16.4",
- "@ledgerhq/logs": "^6.12.0",
- "events": "^3.3.0"
- }
- },
"node_modules/@cityofzion/bs-swap/node_modules/axios": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
@@ -956,14 +922,6 @@
"proxy-from-env": "^1.1.0"
}
},
- "node_modules/@cityofzion/bs-swap/node_modules/isomorphic-ws": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
- "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
- "peerDependencies": {
- "ws": "*"
- }
- },
"node_modules/@cityofzion/dora-ts": {
"version": "0.0.11",
"dependencies": {
diff --git a/package.json b/package.json
index ba569f24..86d27cbe 100644
--- a/package.json
+++ b/package.json
@@ -29,13 +29,13 @@
"playwright:report": "npx playwright show-report"
},
"dependencies": {
- "@cityofzion/blockchain-service": "1.8.0",
+ "@cityofzion/blockchain-service": "1.10.0",
"@cityofzion/bs-asteroid-sdk": "0.9.0",
- "@cityofzion/bs-electron": "0.1.19",
- "@cityofzion/bs-ethereum": "2.7.0",
- "@cityofzion/bs-neo-legacy": "1.6.0",
- "@cityofzion/bs-neo3": "1.8.2",
- "@cityofzion/bs-swap": "0.1.1",
+ "@cityofzion/bs-electron": "0.1.21",
+ "@cityofzion/bs-ethereum": "2.7.2",
+ "@cityofzion/bs-neo-legacy": "1.6.2",
+ "@cityofzion/bs-neo3": "1.8.3",
+ "@cityofzion/bs-swap": "0.2.1",
"@cityofzion/neon-core": "5.5.1",
"@cityofzion/neon-js": "5.5.1",
"@cityofzion/wallet-connect-sdk-wallet-core": "4.4.0",
diff --git a/src/renderer/src/assets/images/simple-swap-logo.png b/src/renderer/src/assets/images/simple-swap-logo.png
deleted file mode 100644
index 66292840..00000000
Binary files a/src/renderer/src/assets/images/simple-swap-logo.png and /dev/null differ
diff --git a/src/renderer/src/components/Details.tsx b/src/renderer/src/components/Details.tsx
new file mode 100644
index 00000000..9b408fcb
--- /dev/null
+++ b/src/renderer/src/components/Details.tsx
@@ -0,0 +1,90 @@
+import { cloneElement, ComponentProps } from 'react'
+import { MdOutlineContentCopy } from 'react-icons/md'
+import { StyleHelper } from '@renderer/helpers/StyleHelper'
+import { UtilsHelper } from '@renderer/helpers/UtilsHelper'
+
+import { IconButton } from './IconButton'
+import { Separator } from './Separator'
+
+type TRootProps = ComponentProps<'div'>
+const Root = ({ className, children, ...props }: TRootProps) => {
+ return (
+
+ {children}
+
+ )
+}
+
+type THeaderProps = {
+ label: string
+ icon?: JSX.Element
+} & ComponentProps<'div'>
+const Header = ({ label, icon, children, ...props }: THeaderProps) => {
+ return (
+
+
+ {icon && cloneElement(icon, { className: StyleHelper.mergeStyles('text-blue w-6 h-6', icon.props.className) })}
+
+ {label}
+
+ {children}
+
+
+
+
+ )
+}
+type TBodyProps = ComponentProps<'div'>
+const Body = ({ className, children, ...props }: TBodyProps) => {
+ return (
+
+ {children}
+
+ )
+}
+
+type TPanelProps = { label: string } & ComponentProps<'div'>
+const Panel = ({ className, children, label, ...props }: TPanelProps) => {
+ return (
+
+
{label}
+
+ {children}
+
+ )
+}
+
+type TItemProps = { label: string; copyable?: boolean } & ComponentProps<'div'>
+const Item = ({ label, children, copyable, className, ...props }: TItemProps) => {
+ const handleCopy = () => {
+ if (typeof children !== 'string') return
+ UtilsHelper.copyToClipboard(children)
+ }
+ return (
+
+
+
{label}
+
+
+ {typeof children === 'string' ? {children} : children}
+
+ {copyable && (
+ }
+ size="sm"
+ onClick={handleCopy}
+ compacted
+ />
+ )}
+
+
+
+
+
+ )
+}
+
+export const Details = { Root, Header, Body, Panel, Item }
diff --git a/src/renderer/src/components/GreyTokenSelect/GreyTokenSelectItem.tsx b/src/renderer/src/components/GreyTokenSelect/GreyTokenSelectItem.tsx
index d7947d5e..1df72ce3 100644
--- a/src/renderer/src/components/GreyTokenSelect/GreyTokenSelectItem.tsx
+++ b/src/renderer/src/components/GreyTokenSelect/GreyTokenSelectItem.tsx
@@ -1,5 +1,6 @@
import { Fragment, useEffect, useState } from 'react'
import defaultTokenLogo from '@renderer/assets/images/default-token-logo.png'
+import { NumberHelper } from '@renderer/helpers/NumberHelper'
import { TGreyTokenSelectToken } from '.'
@@ -30,6 +31,10 @@ export const GreyTokenSelectItem = ({ token }: TProps) => {
{token.symbol}
{token.network && {` | ${token.network}`}}
+
+ {token.amount && (
+ {NumberHelper.formatString(token.amount, 6)}
+ )}
)
}
diff --git a/src/renderer/src/components/GreyTokenSelect/index.tsx b/src/renderer/src/components/GreyTokenSelect/index.tsx
index 275ca8e0..79a75895 100644
--- a/src/renderer/src/components/GreyTokenSelect/index.tsx
+++ b/src/renderer/src/components/GreyTokenSelect/index.tsx
@@ -61,7 +61,9 @@ export const GreyTokenSelect = ({
if (balance) {
filtered = filtered.map(token => {
const tokenHash = UtilsHelper.normalizeHash(token.hash!)
- const tokenBalance = balance.tokensBalances.find(tokenBalance => tokenBalance.token.hash === tokenHash)
+ const tokenBalance = balance.tokensBalances.find(
+ tokenBalance => UtilsHelper.normalizeHash(tokenBalance.token.hash) === tokenHash
+ )
return {
...token,
@@ -144,7 +146,7 @@ export const GreyTokenSelect = ({
position: 'relative',
}}
>
- {rowVirtualizer.getVirtualItems().map(virtualItem => {
+ {rowVirtualizer.getVirtualItems().map((virtualItem, _, array) => {
const row = filteredTokensByText[virtualItem.index]
return (
@@ -162,7 +164,7 @@ export const GreyTokenSelect = ({
-
+ {virtualItem.index + 1 !== array.length && }
)
})}
diff --git a/src/renderer/src/components/Modal/SideModal.tsx b/src/renderer/src/components/Modal/SideModal.tsx
index 40aca88a..0408266f 100644
--- a/src/renderer/src/components/Modal/SideModal.tsx
+++ b/src/renderer/src/components/Modal/SideModal.tsx
@@ -10,8 +10,9 @@ import { ModalContainer } from './ModalContainer'
const widthBySizes: Partial> = {
md: '25.875rem',
sm: '20.625rem',
- xl: '62.5rem',
- lg: '45rem',
+ lg: '32rem',
+ xl: '45rem',
+ '1xl': '62.5rem',
}
export const SideModal = () => {
diff --git a/src/renderer/src/components/Stepper.tsx b/src/renderer/src/components/Stepper.tsx
index 802ca47f..5f48d89e 100644
--- a/src/renderer/src/components/Stepper.tsx
+++ b/src/renderer/src/components/Stepper.tsx
@@ -1,12 +1,23 @@
import { Fragment } from 'react'
import { StyleHelper } from '@renderer/helpers/StyleHelper'
+export type TStepperCurrentState = 'success' | 'error'
+
type TProps = {
steps: string[]
currentStep?: number
+ currentState?: TStepperCurrentState
+ theme?: 'neon' | 'default'
} & React.ComponentProps<'div'>
-export const Stepper = ({ steps, currentStep = 1, className, ...props }: TProps) => {
+export const Stepper = ({
+ steps,
+ currentStep = 1,
+ className,
+ theme = 'default',
+ currentState = 'success',
+ ...props
+}: TProps) => {
return (
{steps.map((step, index) => {
@@ -19,9 +30,12 @@ export const Stepper = ({ steps, currentStep = 1, className, ...props }: TProps)
className={StyleHelper.mergeStyles(
'w-6 h-6 rounded-full flex items-center justify-center text-sm font-bold transition-colors',
{
- 'bg-blue text-asphalt': fixedIndex < currentStep,
- 'bg-white text-asphalt': fixedIndex === currentStep,
- 'bg-gray-900 text-gray-300': fixedIndex > currentStep,
+ 'bg-blue text-asphalt': fixedIndex < currentStep && theme === 'default',
+ 'bg-gray-900 text-gray-300': fixedIndex > currentStep && theme === 'default',
+ 'bg-neon text-asphalt': fixedIndex < currentStep && theme === 'neon',
+ 'bg-gray-300 text-asphalt': fixedIndex > currentStep && theme === 'neon',
+ 'bg-white text-asphalt': fixedIndex === currentStep && currentState === 'success',
+ 'bg-pink text-asphalt': fixedIndex === currentStep && currentState === 'error',
}
)}
>
@@ -32,8 +46,10 @@ export const Stepper = ({ steps, currentStep = 1, className, ...props }: TProps)
className={StyleHelper.mergeStyles(
'text-center w-20 top-8 left-1/2 -translate-x-1/2 text-xs transition-colors absolute',
{
- 'text-blue': fixedIndex < currentStep,
- 'text-white': fixedIndex === currentStep,
+ 'text-blue': fixedIndex < currentStep && theme === 'default',
+ 'text-neon': fixedIndex < currentStep && theme === 'neon',
+ 'text-white': fixedIndex === currentStep && currentState === 'success',
+ 'text-pink': fixedIndex === currentStep && currentState === 'error',
'text-gray-300': fixedIndex > currentStep,
}
)}
@@ -45,8 +61,10 @@ export const Stepper = ({ steps, currentStep = 1, className, ...props }: TProps)
{fixedIndex < steps.length && (
= currentStep,
+ 'border-blue': fixedIndex < currentStep && theme === 'default',
+ 'border-gray-900': fixedIndex >= currentStep && theme === 'default',
+ 'border-neon': fixedIndex < currentStep && theme === 'neon',
+ 'border-gray-300': fixedIndex >= currentStep && theme === 'neon',
})}
/>
)}
diff --git a/src/renderer/src/constants/swap.ts b/src/renderer/src/constants/swap.ts
index 584a53b0..a017f227 100644
--- a/src/renderer/src/constants/swap.ts
+++ b/src/renderer/src/constants/swap.ts
@@ -16,3 +16,5 @@ export const SWAP_NETWORK_BY_BLOCKCHAIN_AND_NETWORK_ID: {
'47763': [''],
},
}
+
+export const SWAP_DISCORD_LINK = 'https://discord.gg/zW26BZC5ku'
diff --git a/src/renderer/src/locales/en/modals.json b/src/renderer/src/locales/en/modals.json
index d8afdcf4..ad2ecd86 100644
--- a/src/renderer/src/locales/en/modals.json
+++ b/src/renderer/src/locales/en/modals.json
@@ -525,5 +525,32 @@
"notConnectedMessage": "No hardware wallet found! Please check your connection and search again",
"connectedMessage": "Detected your hardware wallet! Opening your wallet...",
"searchAgainButtonLabel": "Search again"
+ },
+ "swapDetails": {
+ "title": "Your swap",
+ "swapErrorMessage": " Oops! We’ve encountered an error - swap failed",
+ "transferErrorMessage": " Oops! We’ve encountered an error - transfer failed",
+ "subtitle": "Your swap details",
+ "refundedSubtitle": "You’ve been refunded",
+ "detailsHeaderLabel": "Details",
+ "detailsHeaderDescription": " We aim to complete swaps in under 7 minutes",
+ "statusPanelLabel": "Status",
+ "statusPanelSteps": [
+ "Confirm",
+ "Exchange",
+ "Complete"
+ ],
+ "routingPanelLabel": "Routing",
+ "routingPanelTransactionFromLabel": "Transaction from",
+ "routingPanelTransactionToLabel": "Transaction to",
+ "routingPanelTransactionToLabelPending": "Pending...",
+ "routingPanelTransactionFeeLabel": "Transaction fee",
+ "sentPanelLabel": "You sent",
+ "sentPanelTokenLabel": "Token",
+ "sentPanelAddressLabel": "Sending address",
+ "receivePanelLabel": "You’ll receive",
+ "receivePanelTokenLabel": "Token",
+ "receivePanelAddressLabel": "Receiving address",
+ "helpButtonLabel": "Need help? Chat with us"
}
-}
+}
\ No newline at end of file
diff --git a/src/renderer/src/locales/en/pages.json b/src/renderer/src/locales/en/pages.json
index 224bf9ab..113160fc 100644
--- a/src/renderer/src/locales/en/pages.json
+++ b/src/renderer/src/locales/en/pages.json
@@ -397,7 +397,6 @@
},
"form": {
"title": "What tokens do you want to swap?",
- "simpleSwapImgAlt": "Simple swap",
"swapFromTitle": "Swap this...",
"swapToTitle": "For this...",
"balanceLabel": "Balance",
diff --git a/src/renderer/src/routes/modals/SwapDetails/SwapDetailsModalTokenDetails.tsx b/src/renderer/src/routes/modals/SwapDetails/SwapDetailsModalTokenDetails.tsx
new file mode 100644
index 00000000..495448bb
--- /dev/null
+++ b/src/renderer/src/routes/modals/SwapDetails/SwapDetailsModalTokenDetails.tsx
@@ -0,0 +1,25 @@
+import { useTranslation } from 'react-i18next'
+import { BlockchainIcon } from '@renderer/components/BlockchainIcon'
+import { TBlockchainServiceKey } from '@shared/@types/blockchain'
+
+type TProps = {
+ blockchain?: TBlockchainServiceKey
+ symbol: string
+ amount: string
+}
+
+export const SwapDetailsModalTokenDetails = ({ amount, blockchain, symbol }: TProps) => {
+ const { t: commonT } = useTranslation('common')
+
+ return (
+
+ {blockchain && }
+
+ {symbol}
+ {blockchain && | {commonT(`blockchain.${blockchain}`)}}
+
+
+ {amount}
+
+ )
+}
diff --git a/src/renderer/src/routes/modals/SwapDetails/index.tsx b/src/renderer/src/routes/modals/SwapDetails/index.tsx
new file mode 100644
index 00000000..0e212962
--- /dev/null
+++ b/src/renderer/src/routes/modals/SwapDetails/index.tsx
@@ -0,0 +1,214 @@
+import { useEffect, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { MdLaunch, MdRefresh } from 'react-icons/md'
+import { TbCircleX, TbDiscountCheck, TbReceipt, TbReplace } from 'react-icons/tb'
+import { SimpleSwapServiceHelper } from '@cityofzion/bs-swap'
+import { BlockchainIcon } from '@renderer/components/BlockchainIcon'
+import { Button } from '@renderer/components/Button'
+import { Details } from '@renderer/components/Details'
+import { Separator } from '@renderer/components/Separator'
+import { Stepper, TStepperCurrentState } from '@renderer/components/Stepper'
+import { SWAP_DISCORD_LINK } from '@renderer/constants/swap'
+import { StringHelper } from '@renderer/helpers/StringHelper'
+import { useModalState } from '@renderer/hooks/useModalRouter'
+import { useAppDispatch } from '@renderer/hooks/useRedux'
+import { SideModalLayout } from '@renderer/layouts/SideModal'
+import { bsAggregator } from '@renderer/libs/blockchainService'
+import { authReducerActions } from '@renderer/store/reducers/AuthReducer'
+import { TSwapRecord } from '@shared/@types/store'
+import { match, P } from 'ts-pattern'
+
+import { SwapDetailsModalTokenDetails } from './SwapDetailsModalTokenDetails'
+
+type TState = {
+ swapRecord: TSwapRecord
+}
+
+const swapServiceHelper = new SimpleSwapServiceHelper(import.meta.env.VITE_SIMPLE_SWAP_API_KEY ?? '')
+
+const stepsByStatus: Record
= {
+ confirming: 2,
+ exchanging: 3,
+ finished: 4,
+ failed: 2,
+ refunded: 2,
+}
+
+export const SwapDetailsModal = () => {
+ const modalState = useModalState()
+ const dispatch = useAppDispatch()
+ const { t } = useTranslation('modals', { keyPrefix: 'swapDetails' })
+
+ const [swapRecord, setSwapRecord] = useState(modalState.swapRecord)
+
+ const timeoutRef = useRef()
+
+ const service = bsAggregator.blockchainServicesByName[swapRecord.account.blockchain]
+
+ const handleHelpClick = () => {
+ window.open(SWAP_DISCORD_LINK, '_blank')
+ }
+
+ useEffect(() => {
+ const getStatus = async () => {
+ if (!swapRecord.swapId) return
+
+ try {
+ const response = await swapServiceHelper.getStatus(swapRecord.swapId)
+
+ const updatedSwapRecord: TSwapRecord = { ...swapRecord, swapStatus: 'failed', txTo: response.txTo }
+ setSwapRecord(updatedSwapRecord)
+ dispatch(authReducerActions.persistSwapRecord(updatedSwapRecord))
+
+ if (response && response.status === 'finished') {
+ clearTimeout(timeoutRef.current)
+ return
+ }
+ } catch {
+ // Empty block
+ }
+
+ timeoutRef.current = setTimeout(getStatus, 2500)
+ }
+
+ if (swapRecord.swapStatus !== 'finished' && swapRecord.swapId) timeoutRef.current = setTimeout(getStatus, 0)
+
+ return () => {
+ clearTimeout(timeoutRef.current)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ return (
+ }
+ contentClassName="flex flex-col items-center overflow-auto"
+ >
+
+ {swapRecord.swapStatus === 'failed' || swapRecord.swapStatus === 'refunded' ? (
+
+ ) : (
+
+ )}
+
+
+
+ {match({ swapStatus: swapRecord.swapStatus, txFrom: swapRecord.txFrom })
+ .with({ swapStatus: 'refunded', txFrom: P.nullish }, () => (
+
{t('transferErrorMessage')}
+ ))
+ .with({ swapStatus: P.union('refunded', 'failed'), txFrom: P.string }, () => (
+
{t('swapErrorMessage')}
+ ))
+ .run()}
+
+
+ {match({ swapStatus: swapRecord.swapStatus })
+ .with({ swapStatus: 'refunded' }, () => t('refundedSubtitle'))
+ .otherwise(() => t('subtitle'))}
+
+
+
+
+
+
+ }>
+ {t('detailsHeaderDescription')}
+
+
+
+
+ ()
+ .with({ swapStatus: P.union('failed', 'refunded') }, () => 'error')
+ .with({ txFrom: P.nullish }, () => 'error')
+ .otherwise(() => 'success')}
+ theme="neon"
+ />
+
+
+
+ {swapRecord.txFrom && (
+
+
+
+ {StringHelper.truncateStringMiddle(swapRecord.txFrom, 50)}
+
+
+ )}
+
+ {swapRecord.txFrom && (
+
+ {match({ txTo: swapRecord.txTo })
+ .with({ txTo: P.string }, ({ txTo }) => (
+
+ {swapRecord.tokenTo.blockchain && }
+ {StringHelper.truncateStringMiddle(txTo, 50)}
+
+ ))
+ .otherwise(() => (
+
+ {t('routingPanelTransactionToLabelPending')}
+
+
+ ))}
+
+ )}
+
+ {swapRecord.fee && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {swapRecord.account.address}
+
+
+
+
+
+
+
+ {swapRecord.addressTo}
+
+
+
+
+ }
+ wide
+ />
+
+ )
+}
diff --git a/src/renderer/src/routes/modalsRouter.tsx b/src/renderer/src/routes/modalsRouter.tsx
index 3a59d2ab..5c90a12e 100644
--- a/src/renderer/src/routes/modalsRouter.tsx
+++ b/src/renderer/src/routes/modalsRouter.tsx
@@ -43,6 +43,7 @@ import { DeleteModal } from './modals/PersistContact/DeleteModal'
import { SelectAccountModal } from './modals/SelectAccount'
import { SelectContact } from './modals/SelectContact'
import { SuccessModal } from './modals/Success'
+import { SwapDetailsModal } from './modals/SwapDetails'
export const modalsRouter: TRoute[] = [
{ name: 'import', type: 'side', element: },
@@ -57,10 +58,10 @@ export const modalsRouter: TRoute[] = [
{ name: 'export-mnemonic', type: 'side', size: 'md', element: },
{ name: 'confirm-password-export', type: 'side', element: },
{ name: 'delete-wallet', type: 'side', size: 'md', element: },
- { name: 'create-wallet-step-1', type: 'side', size: 'xl', element: },
- { name: 'create-wallet-step-2', type: 'side', size: 'xl', element: },
- { name: 'create-wallet-step-3', type: 'side', size: 'xl', element: },
- { name: 'create-wallet-step-4', type: 'side', size: 'xl', element: },
+ { name: 'create-wallet-step-1', type: 'side', size: '1xl', element: },
+ { name: 'create-wallet-step-2', type: 'side', size: '1xl', element: },
+ { name: 'create-wallet-step-3', type: 'side', size: '1xl', element: },
+ { name: 'create-wallet-step-4', type: 'side', size: '1xl', element: },
{ name: 'persist-contact', type: 'side', element: },
{ name: 'delete-contact', type: 'side', element: },
{ name: 'add-address', type: 'side', element: },
@@ -81,9 +82,9 @@ export const modalsRouter: TRoute[] = [
{ name: 'decrypt-key', type: 'side', element: },
{ name: 'select-account', type: 'side', element: },
{ name: 'network-selection', type: 'side', element: },
- { name: 'migrate-accounts-step-2', type: 'side', size: 'lg', element: },
- { name: 'migrate-accounts-step-3', type: 'side', size: 'lg', element: },
- { name: 'migrate-accounts-step-4', type: 'side', size: 'lg', element: },
+ { name: 'migrate-accounts-step-2', type: 'side', size: 'xl', element: },
+ { name: 'migrate-accounts-step-3', type: 'side', size: 'xl', element: },
+ { name: 'migrate-accounts-step-4', type: 'side', size: 'xl', element: },
{ name: 'auto-update-completed', type: 'center', size: 'lg', element: },
{ name: 'auto-update-mobile', type: 'center', size: 'lg', element: },
{ name: 'auto-update-notes', type: 'center', size: 'lg', element: },
@@ -92,4 +93,5 @@ export const modalsRouter: TRoute[] = [
{ name: 'add-network-profile', type: 'side', element: },
{ name: 'nft-selection', type: 'side', element: },
{ name: 'connect-hardware-wallet', type: 'center', size: 'lg', element: },
+ { name: 'swap-details', type: 'side', size: 'lg', element: },
]
diff --git a/src/renderer/src/routes/pages/Swap/SwapErrorModalContent.tsx b/src/renderer/src/routes/pages/Swap/SwapErrorModalContent.tsx
deleted file mode 100644
index 320884c1..00000000
--- a/src/renderer/src/routes/pages/Swap/SwapErrorModalContent.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useTranslation } from 'react-i18next'
-
-type TProps = {
- error: string
-}
-
-export const SwapErrorModalContent = ({ error }: TProps) => {
- const { t } = useTranslation('modals', { keyPrefix: 'dappPermission' })
-
- return (
-
-
{t('errorModal.text')}
-
-
-
{t('errorModal.errorMessageLabel')}
-
- {error}
-
-
-
- )
-}
diff --git a/src/renderer/src/routes/pages/Swap/SwapPageContent.tsx b/src/renderer/src/routes/pages/Swap/SwapPageContent.tsx
index 5766514b..7787bff6 100644
--- a/src/renderer/src/routes/pages/Swap/SwapPageContent.tsx
+++ b/src/renderer/src/routes/pages/Swap/SwapPageContent.tsx
@@ -1,7 +1,7 @@
import { ChangeEvent, Fragment, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { MdArrowForward, MdInfoOutline } from 'react-icons/md'
-import { TbArrowDown, TbReplace, TbStepInto, TbStepOut, TbUsers, TbWallet } from 'react-icons/tb'
+import { TbArrowDown, TbStepInto, TbStepOut, TbUsers, TbWallet } from 'react-icons/tb'
import { VscCircleFilled } from 'react-icons/vsc'
import {
Account,
@@ -13,7 +13,6 @@ import {
SwapServiceValidateValue,
} from '@cityofzion/blockchain-service'
import { SimpleSwapService } from '@cityofzion/bs-swap'
-import simpleSwapLogo from '@renderer/assets/images/simple-swap-logo.png'
import { ActionStep } from '@renderer/components/ActionStep'
import { AlertErrorBanner } from '@renderer/components/AlertErrorBanner'
import { Button } from '@renderer/components/Button'
@@ -40,9 +39,6 @@ import { authReducerActions } from '@renderer/store/reducers/AuthReducer'
import { TBlockchainServiceKey } from '@shared/@types/blockchain'
import { IAccountState, TContactAddress, TSwapRecord } from '@shared/@types/store'
-import { SwapErrorModalContent } from './SwapErrorModalContent'
-import { SwapSuccessModalContent } from './SwapSuccessModalContent'
-
type TActionsData = {
availableTokensToUse: SwapServiceLoadableValue[]>
selectedTokenToUse: SwapServiceLoadableValue>
@@ -200,46 +196,34 @@ export const SwapPageContent = ({ account }: TProps) => {
return
}
+ const swapRecord: TSwapRecord = {
+ account: actionData.selectedAccountToUse.value,
+ addressTo: actionData.selectedAddressToReceive.value,
+ amountTo: actionData.selectedAmountToUse.value,
+ amountFrom: actionData.selectedAmountToReceive.value,
+ tokenTo: actionData.selectedTokenToUse.value,
+ tokenFrom: actionData.selectedTokenToReceive.value,
+ swapStatus: 'confirming',
+ swapProvider: 'simpleswap',
+ fee: actionData.fee,
+ }
+
try {
const swapResponse = await swapServiceRef.current.swap()
-
- const swapRecord: TSwapRecord = {
- account: actionData.selectedAccountToUse.value,
- addressTo: actionData.selectedAddressToReceive.value,
- amountTo: actionData.selectedAmountToUse.value,
- amountFrom: actionData.selectedAmountToReceive.value,
- tokenTo: actionData.selectedTokenToUse.value,
- tokenFrom: actionData.selectedTokenToReceive.value,
- swapStatus: 'confirming',
- swapProvider: 'simpleswap',
- swapId: swapResponse.id,
- transactionHashes: [swapResponse.transactionHash],
- numberOfTransactions: swapResponse.numberOfTransactions,
- fee: actionData.fee,
- }
-
- dispatch(authReducerActions.addSwapRecord(swapRecord))
-
- modalNavigate('success', {
- state: {
- heading: t('title'),
- headingIcon: ,
- subtitle: t('form.sendSuccess.subtitle'),
- content: ,
- },
- })
+ swapRecord.txFrom = swapResponse.transactionHash
+ swapRecord.txTo = swapResponse.id
} catch (error: any) {
console.error(error)
- modalNavigate('error', {
+ swapRecord.swapStatus = 'refunded'
+ } finally {
+ dispatch(authReducerActions.persistSwapRecord(swapRecord))
+
+ modalNavigate('swap-details', {
state: {
- heading: t('title'),
- headingIcon: ,
- subtitle: t('form.sendError.subtitle'),
- description: t('form.sendError.description'),
- content: ,
+ swapRecord,
},
})
- } finally {
+
reset()
}
}
@@ -447,11 +431,6 @@ export const SwapPageContent = ({ account }: TProps) => {
{t('explanation.description2')}
-
-
-
{t('explanation.useTitle')}
-
-
diff --git a/src/renderer/src/routes/pages/Swap/SwapSuccessModalContent.tsx b/src/renderer/src/routes/pages/Swap/SwapSuccessModalContent.tsx
deleted file mode 100644
index 9bf631ca..00000000
--- a/src/renderer/src/routes/pages/Swap/SwapSuccessModalContent.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { Fragment, ReactNode, useEffect, useRef, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { TbReceipt } from 'react-icons/tb'
-import { SwapService, SwapServiceStatusResponse } from '@cityofzion/blockchain-service'
-import { Loader } from '@renderer/components/Loader'
-import { Separator } from '@renderer/components/Separator'
-import { bsAggregator } from '@renderer/libs/blockchainService'
-import { TBlockchainServiceKey } from '@shared/@types/blockchain'
-import { TSwapRecord } from '@shared/@types/store'
-
-type TProps = {
- swapService: SwapService
- swapRecord: TSwapRecord
-}
-
-type TItemProps = {
- label: string
- value: ReactNode
-}
-const Item = ({ label, value }: TItemProps) => {
- return (
-
- {label}
-
- {value}
-
- )
-}
-
-export const SwapSuccessModalContent = ({ swapRecord, swapService }: TProps) => {
- const { t } = useTranslation('pages', { keyPrefix: 'swap.form.sendSuccess' })
- const [statusResponse, setStatusResponse] = useState({
- status: swapRecord.swapStatus,
- transactionHashes: swapRecord.transactionHashes,
- })
-
- const timeoutRef = useRef()
-
- useEffect(() => {
- const getStatus = async () => {
- let response: SwapServiceStatusResponse | undefined
- try {
- response = await swapService.getStatus()
- setStatusResponse(response)
- } catch {
- // Empty block
- }
-
- if (response && response.status === 'finished') {
- clearTimeout(timeoutRef.current)
- return
- }
-
- timeoutRef.current = setTimeout(getStatus, 2500)
- }
-
- timeoutRef.current = setTimeout(getStatus, 0)
-
- return () => {
- clearTimeout(timeoutRef.current)
- }
- }, [swapService])
-
- return (
-
-
-
-
-
- {t('detailsTitle')}
-
-
-
-
-
- -
- {statusResponse.status}
- {statusResponse.status !== 'finished' && statusResponse.status !== 'failed' && (
-
- )}
-
- }
- />
-
-
-
-
-
-
-
-
-
- {swapRecord.amountFrom}
- {swapRecord.tokenFrom.symbol}
-
- }
- />
-
-
-
-
-
- {swapRecord.amountTo}
- {swapRecord.tokenTo.symbol}
-
- }
- />
-
-
-
- {swapRecord.fee && (
-
-
- {swapRecord.fee}
-
- {bsAggregator.blockchainServicesByName[swapRecord.account.blockchain].feeToken.symbol}
-
-
- }
- />
- )}
-
-
-
- {statusResponse.transactionHashes.map((hash, index, array) => {
- const label =
- index === 0
- ? t('transactionFromLabel')
- : index + 1 === array.length
- ? t('transactionToLabel')
- : t('transactionAux')
-
- return (
-
-
-
- {index + 1 !== array.length && }
-
- )
- })}
-
-
-
-
- )
-}
diff --git a/src/renderer/src/store/reducers/AuthReducer.ts b/src/renderer/src/store/reducers/AuthReducer.ts
index 1362e71f..1adafef3 100644
--- a/src/renderer/src/store/reducers/AuthReducer.ts
+++ b/src/renderer/src/store/reducers/AuthReducer.ts
@@ -231,8 +231,19 @@ const addPendingTransaction = createAsyncThunk<
}
})
-const addSwapRecord: CaseReducer> = (state, action) => {
- state.data.swapRecords = [...state.data.swapRecords, action.payload]
+const persistSwapRecord: CaseReducer> = (state, action) => {
+ const swapRecord = action.payload
+
+ const swapRecordIndex = state.data.swapRecords.findIndex(
+ it => it.swapId === swapRecord.swapId && it.swapProvider === swapRecord.swapProvider
+ )
+
+ if (swapRecordIndex === -1) {
+ state.data.swapRecords = [...state.data.swapRecords, swapRecord]
+ return
+ }
+
+ state.data.swapRecords[swapRecordIndex] = swapRecord
}
const AuthReducer = createSlice({
@@ -245,7 +256,7 @@ const AuthReducer = createSlice({
deleteAccount,
setCurrentLoginSession,
resetTemporaryApplicationData,
- addSwapRecord,
+ persistSwapRecord,
},
extraReducers: builder => {
builder.addCase(PURGE, () => initialState)
diff --git a/src/shared/@types/i18next-resources.d.ts b/src/shared/@types/i18next-resources.d.ts
index c4e98cab..19a9c6d3 100644
--- a/src/shared/@types/i18next-resources.d.ts
+++ b/src/shared/@types/i18next-resources.d.ts
@@ -777,6 +777,29 @@ interface Resources {
connectedMessage: 'Detected your hardware wallet! Opening your wallet...'
searchAgainButtonLabel: 'Search again'
}
+ swapDetails: {
+ title: 'Your swap'
+ swapErrorMessage: ' Oops! We’ve encountered an error - swap failed'
+ transferErrorMessage: ' Oops! We’ve encountered an error - transfer failed'
+ subtitle: 'Your swap details'
+ refundedSubtitle: 'You’ve been refunded'
+ detailsHeaderLabel: 'Details'
+ detailsHeaderDescription: ' We aim to complete swaps in under 7 minutes'
+ statusPanelLabel: 'Status'
+ statusPanelSteps: ['Confirm', 'Exchange', 'Complete']
+ routingPanelLabel: 'Routing'
+ routingPanelTransactionFromLabel: 'Transaction from'
+ routingPanelTransactionToLabel: 'Transaction to'
+ routingPanelTransactionToLabelPending: 'Pending...'
+ routingPanelTransactionFeeLabel: 'Transaction fee'
+ sentPanelLabel: 'You sent'
+ sentPanelTokenLabel: 'Token'
+ sentPanelAddressLabel: 'Sending address'
+ receivePanelLabel: 'You’ll receive'
+ receivePanelTokenLabel: 'Token'
+ receivePanelAddressLabel: 'Receiving address'
+ helpButtonLabel: 'Need help? Chat with us'
+ }
}
pages: {
welcome: {
@@ -1167,7 +1190,6 @@ interface Resources {
}
form: {
title: 'What tokens do you want to swap?'
- simpleSwapImgAlt: 'Simple swap'
swapFromTitle: 'Swap this...'
swapToTitle: 'For this...'
balanceLabel: 'Balance'
diff --git a/src/shared/@types/modal.ts b/src/shared/@types/modal.ts
index 09ec97f3..8c4abf78 100644
--- a/src/shared/@types/modal.ts
+++ b/src/shared/@types/modal.ts
@@ -1,5 +1,5 @@
export type TRouteType = 'side' | 'center'
-export type TRouterSize = 'sm' | 'md' | 'lg' | 'xl'
+export type TRouterSize = 'sm' | 'md' | 'lg' | 'xl' | '1xl'
export type TRoute = {
element: JSX.Element
diff --git a/src/shared/@types/store.ts b/src/shared/@types/store.ts
index 4f6e676c..58e77391 100644
--- a/src/shared/@types/store.ts
+++ b/src/shared/@types/store.ts
@@ -91,15 +91,15 @@ export interface IContactState {
export type TSwapRecord = {
account: IAccountState
- transactionHashes: string[]
+ txFrom?: string
+ txTo?: string
swapProvider: 'simpleswap'
- swapId: string
+ swapId?: string
swapStatus: SwapServiceStatusResponse['status']
- tokenFrom: SwapServiceToken
- tokenTo: SwapServiceToken
+ tokenFrom: SwapServiceToken
+ tokenTo: SwapServiceToken
amountFrom: string
amountTo: string
addressTo: string
- numberOfTransactions: number
fee?: string
}